[{"data":1,"prerenderedAt":1447},["ShallowReactive",2],{"page-\u002Fcpp\u002Fegl和skia":3},{"id":4,"title":5,"body":6,"description":1440,"extension":1441,"meta":1442,"navigation":215,"path":1443,"seo":1444,"stem":1445,"__hash__":1446},"content\u002Fcpp\u002FEGL和Skia.md","EGL和Skia",{"type":7,"value":8,"toc":1429},"minimark",[9,35,38,45,48,70,75,78,85,88,95,103,110,113,119,129,161,164,167,172,191,194,267,270,273,279,283,344,354,362,369,428,431,459,473,476,516,519,522,525,567,585,588,634,679,686,689,692,753,767,770,776,779,782,788,791,794,802,805,808,852,855,858,861,882,885,888,891,977,980,983,994,997,1070,1073,1136,1139,1142,1204,1207,1210,1213,1311,1329,1332,1347,1354,1357,1360,1380,1383,1422,1425],[10,11,12,19,20,25,26,30,31,34],"p",{},[13,14,18],"a",{"href":15,"rel":16},"https:\u002F\u002Fwww.opengl.org\u002F",[17],"nofollow","OpenGL"," 是一种跨平台的图形 API，用于为 3D 图形处理硬件指定标准的软件接口。",[13,21,24],{"href":22,"rel":23},"https:\u002F\u002Fwww.khronos.org\u002Fopengles\u002F",[17],"OpenGL ES"," 是 OpenGL 规范的子集，适用于嵌入式设备。而EGL 是",[27,28,29],"strong",{},"渲染 API（如 OpenGL ES）和原生窗口系统之间的接口",".通常来说，OpenGL 是一个操作 GPU 的 API，它通过驱动向 GPU 发送相关指令，控制图形渲染管线状态机的运行状态，但是当涉及到与",[27,32,33],{},"本地窗口系统进行交互时","，就需要这么一个中间层，且它最好是与平台无关的,",[10,36,37],{},"因此 EGL 被设计出来，作为 OpenGL 和原生窗口系统之间的桥梁：![[Pasted image 20251231232401.png]]",[10,39,40,41,44],{},"EGL API 是",[27,42,43],{},"独立于 OpenGL 各版本标准的独立的一套 API","，其主要作用是**为 OpenGL 指令 创建上下文 Context 、绘制目标 Surface 、配置 FrameBuffer 属性、Swap 提交绘制结果等。",[10,46,47],{},"EGL提供如下机制：",[49,50,51,55,58,61,64],"ul",{},[52,53,54],"li",{},"与设备的原生窗口系统通信",[52,56,57],{},"查询绘图表面的可用类型和配置",[52,59,60],{},"创建绘图表面",[52,62,63],{},"在 OpenGL ES 和其他图形渲染 API 之间同步渲染",[52,65,66,67],{},"管理",[27,68,69],{},"纹理贴图等渲染资源",[71,72,74],"h2",{"id":73},"egl的三大支柱","EGL的三大支柱",[10,76,77],{},"下面这张图可简要看出EGL的接口能力：![[Pasted image 20251231232442.png]]",[10,79,80,81,84],{},"如果我们想要在安装上使用opengl es，我们可以直接用GLSurfaceView来进行Opengl的渲染，因为GLSurfaceView内部给我们",[27,82,83],{},"封装好了EGL环境和渲染线程","。",[10,86,87],{},"![[Pasted image 20251231232801.png]]",[10,89,90,91,94],{},"EGL 的主要任务是",[27,92,93],{},"管理渲染所需的资源","，Disyplay是显示连接的功能，可以为我们提供获取显示设备的句柄（Windows上是HDC）， 初始化显示连接等。",[10,96,97,98,102],{},"Surface是OpenGL画图的地方，本质是一个内存缓冲区，他可以将EGL和本地窗口通过我们在GLFW拿到的HWND绑定在一起，",[99,100,101],"code",{},"eglCreatePBufferSurface()","甚至可以创建一个离屏缓冲区（不显示在屏幕上）。",[10,104,105,106,109],{},"EGL可以利用一个渲染上下文，存储所有opengel状态的容器，比如说当前使用的着色器、纹理、顶点数据等，",[27,107,108],{},"EGL是创建OpenGL ES渲染上下文所必需的","。这个上下文必须连接到合适的表面才能开始渲染。",[71,111,112],{"id":112},"单线程模型",[10,114,115,116,118],{},"EGL是",[27,117,112],{},"的，也就说说EGL环境的创建、渲染操作、EGL环境的销毁都必须在同一个线程内完成，否则是无效的。我们可以通过共享EGL上下文来做多多线程渲染。",[10,120,121,122,125,126,84],{},"EGL本身其实是支持多线程的，但是他采用了一个叫做 ",[27,123,124],{},"线程局部"," 的绑定模型，这种模型确实决定了在大多数 UI 框架（如 GLFW + ANGLE 项目）中，",[27,127,128],{},"将 UI 逻辑与渲染逻辑保持在同一个线程是最稳妥、最高效的选择",[130,131,132,139],"blockquote",{},[10,133,134,138],{},[135,136,137],"span",{},"!tip"," 顺带一提，UI线程单线程话也是工业界普遍的做法，比如说安卓的UI线程，或者说Windows的消息循环。",[10,140,141,144,145,148,149,152,153,156,157,160],{},[27,142,143],{},"窗口句柄的线程依赖","：底层的 ",[27,146,147],{},"Native Window System","（如 GLFW 创建的窗口）通常具有线程亲和性。在 Windows 上，只有创建窗口的线程才能可靠地接收和处理该窗口的消息。\n",[27,150,151],{},"Surface 的同步","：",[27,154,155],{},"Surface (S)"," 是连接 OpenGL 和本地窗口的纽带。如果渲染在线程 B，而窗口缩放（DPI 变化）在线程 A 处理，那么在调用 ",[99,158,159],{},"eglSwapBuffers"," 时，可能会出现缓冲区大小不匹配导致的闪烁或崩溃。",[10,162,163],{},"在我们使用EGL的时候，我们会去建立与GPU的连接（以便以Display的获取）",[71,165,166],{"id":166},"渲染过程",[168,169,171],"h3",{"id":170},"建立gpu连接","建立GPU连接",[10,173,174,175,178,179,182,183,186,187,190],{},"这是整个流程的第一步。如果使用的是 ANGLE 扩展，不能使用标准的 ",[99,176,177],{},"eglGetDisplay","，而必须使用 ",[99,180,181],{},"eglGetPlatformDisplayEXT","，我们可以通过 ",[99,184,185],{},"displayAttributes"," 指定 ANGLE 使用 ",[27,188,189],{},"D3D11"," 作为底层驱动。",[10,192,193],{},"我们一般来说，会使用pimpl设计模式，做一个渲染环境内部的封装器，将复杂的gpu状态管理隐藏在外部类外。",[195,196,201],"pre",{"className":197,"code":198,"language":199,"meta":200,"style":200},"language-cpp shiki shiki-themes github-light github-dark","    struct RenderContext::Impl {\n\n        EGLDisplay display_ = EGL_NO_DISPLAY; \u002F\u002F EGL display\n\n        EGLConfig config_ = nullptr; \u002F\u002F EGL config\n\n        EGLContext context_ = EGL_NO_CONTEXT; \u002F\u002F EGL context\n\n        sk_sp\u003CGrDirectContext> skiaContext_ = nullptr;\n\n    };\n","cpp","",[99,202,203,210,217,223,228,234,239,245,250,256,261],{"__ignoreMap":200},[135,204,207],{"class":205,"line":206},"line",1,[135,208,209],{},"    struct RenderContext::Impl {\n",[135,211,213],{"class":205,"line":212},2,[135,214,216],{"emptyLinePlaceholder":215},true,"\n",[135,218,220],{"class":205,"line":219},3,[135,221,222],{},"        EGLDisplay display_ = EGL_NO_DISPLAY; \u002F\u002F EGL display\n",[135,224,226],{"class":205,"line":225},4,[135,227,216],{"emptyLinePlaceholder":215},[135,229,231],{"class":205,"line":230},5,[135,232,233],{},"        EGLConfig config_ = nullptr; \u002F\u002F EGL config\n",[135,235,237],{"class":205,"line":236},6,[135,238,216],{"emptyLinePlaceholder":215},[135,240,242],{"class":205,"line":241},7,[135,243,244],{},"        EGLContext context_ = EGL_NO_CONTEXT; \u002F\u002F EGL context\n",[135,246,248],{"class":205,"line":247},8,[135,249,216],{"emptyLinePlaceholder":215},[135,251,253],{"class":205,"line":252},9,[135,254,255],{},"        sk_sp\u003CGrDirectContext> skiaContext_ = nullptr;\n",[135,257,259],{"class":205,"line":258},10,[135,260,216],{"emptyLinePlaceholder":215},[135,262,264],{"class":205,"line":263},11,[135,265,266],{},"    };\n",[10,268,269],{},"display_就是代表着和物理显示器的连接在windows上，他通过ANGLE代理了与D3D11的设备的交互，config_则是代表配置好的画布属性（像素格式、位深、采样率等）。",[10,271,272],{},"context_是最重要的GPU的状态机，存储了所有的着色器、纹理和渲染状态，是绘图命令执行的核心环节，可以用来接收GLES的指令。",[10,274,275,278],{},[99,276,277],{},"sk_sp\u003CGrDirectContext> skiaContext_"," 是Skia 库的 GPU 上下文入口，它封装了底层的 OpenGL ES 调用，负责管理 GPU 资源（如纹理缓存）。",[168,280,282],{"id":281},"angel的初始化","ANGEL的初始化",[195,284,286],{"className":197,"code":285,"language":199,"meta":200,"style":200},"    bool RenderContext::Initialize() {\n        auto eglGetPlatformDisplayEXT = reinterpret_cast\u003CPFNEGLGETPLATFORMDISPLAYEXTPROC>(eglGetProcAddress(\"eglGetPlatformDisplayEXT\"));\n\n        const EGLint displayAttributes[] = {\n\n            EGL_PLATFORM_ANGLE_TYPE_ANGLE, EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE,\n\n            EGL_PLATFORM_ANGLE_ENABLE_AUTOMATIC_TRIM_ANGLE, EGL_TRUE,\n\n            EGL_NONE\n\n        };\n",[99,287,288,293,298,302,307,311,316,320,325,329,334,338],{"__ignoreMap":200},[135,289,290],{"class":205,"line":206},[135,291,292],{},"    bool RenderContext::Initialize() {\n",[135,294,295],{"class":205,"line":212},[135,296,297],{},"        auto eglGetPlatformDisplayEXT = reinterpret_cast\u003CPFNEGLGETPLATFORMDISPLAYEXTPROC>(eglGetProcAddress(\"eglGetPlatformDisplayEXT\"));\n",[135,299,300],{"class":205,"line":219},[135,301,216],{"emptyLinePlaceholder":215},[135,303,304],{"class":205,"line":225},[135,305,306],{},"        const EGLint displayAttributes[] = {\n",[135,308,309],{"class":205,"line":230},[135,310,216],{"emptyLinePlaceholder":215},[135,312,313],{"class":205,"line":236},[135,314,315],{},"            EGL_PLATFORM_ANGLE_TYPE_ANGLE, EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE,\n",[135,317,318],{"class":205,"line":241},[135,319,216],{"emptyLinePlaceholder":215},[135,321,322],{"class":205,"line":247},[135,323,324],{},"            EGL_PLATFORM_ANGLE_ENABLE_AUTOMATIC_TRIM_ANGLE, EGL_TRUE,\n",[135,326,327],{"class":205,"line":252},[135,328,216],{"emptyLinePlaceholder":215},[135,330,331],{"class":205,"line":258},[135,332,333],{},"            EGL_NONE\n",[135,335,336],{"class":205,"line":263},[135,337,216],{"emptyLinePlaceholder":215},[135,339,341],{"class":205,"line":340},12,[135,342,343],{},"        };\n",[10,345,346,347,349,350,353],{},"它通过反射和属性子带你来配置显卡的驱动的运行模式，",[99,348,181],{}," 并不是标准 EGL 1.4 的函数，而是一个",[27,351,352],{},"扩展 (Extension)","。 这组数组定义了你希望 ANGLE 如何在你的 Windows 系统上工作：",[195,355,360],{"className":356,"code":358,"language":359},[357],"language-text","const EGLint displayAttributes[] = {\n    EGL_PLATFORM_ANGLE_TYPE_ANGLE, EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE,\n    EGL_PLATFORM_ANGLE_ENABLE_AUTOMATIC_TRIM_ANGLE, EGL_TRUE,\n    EGL_NONE\n};\n","text",[99,361,358],{"__ignoreMap":200},[10,363,364,152],{},[27,365,366],{},[99,367,368],{},"EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE",[49,370,371,381,396,404,422],{},[52,372,373,376,377,380],{},[27,374,375],{},"核心作用","：强制 ANGLE 使用 ",[27,378,379],{},"Direct3D 11"," 作为翻译目标。",[52,382,383,386,387,390,391,152],{},[27,384,385],{},"意义","：这意味着你写的 OpenGL ES 指令最终会被",[27,388,389],{},"转化为 D3D11 指令运行","。在 Windows 上，D3D11 的驱动支持通常比原生 OpenGL 更好、更稳定。\n",[27,392,393],{},[99,394,395],{},"EGL_PLATFORM_ANGLE_ENABLE_AUTOMATIC_TRIM_ANGLE",[52,397,398,400,401,84],{},[27,399,375],{},"：开启",[27,402,403],{},"自动内存释放（Trim）",[52,405,406,408,409,412,413,416,417,152],{},[27,407,385],{},"：当",[27,410,411],{},"窗口最小化或程序进入后台时","，ANGLE 会",[27,414,415],{},"自动释放一些占用的 GPU 内存资源","。这对于桌面应用程序的性能优化非常重要。\n",[27,418,419],{},[99,420,421],{},"EGL_NONE",[52,423,424,427],{},[27,425,426],{},"作用","：这是一个终止符，告诉 EGL 这个属性列表结束了。",[10,429,430],{},"创建显示连接：",[195,432,434],{"className":197,"code":433,"language":199,"meta":200,"style":200}," impl_->display_ = eglGetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE,\n\n            EGL_DEFAULT_DISPLAY,\n\n            displayAttributes);\n",[99,435,436,441,445,450,454],{"__ignoreMap":200},[135,437,438],{"class":205,"line":206},[135,439,440],{}," impl_->display_ = eglGetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE,\n",[135,442,443],{"class":205,"line":212},[135,444,216],{"emptyLinePlaceholder":215},[135,446,447],{"class":205,"line":219},[135,448,449],{},"            EGL_DEFAULT_DISPLAY,\n",[135,451,452],{"class":205,"line":225},[135,453,216],{"emptyLinePlaceholder":215},[135,455,456],{"class":205,"line":230},[135,457,458],{},"            displayAttributes);\n",[10,460,461,462,465,466,469,470,84],{},"它接收了 ",[99,463,464],{},"EGL_PLATFORM_ANGLE_ANGLE","（告诉它我要用 ANGLE 平台）和刚才定义的一系列属性，会去返回一个 ",[99,467,468],{},"EGLDisplay"," 句柄。这一步成功执行后，你的程序就正式与显卡驱动建立了一条",[27,471,472],{},"基于 D3D11 后端的渲染链路",[10,474,475],{},"EGL的握手和版本协商阶段：",[195,477,479],{"className":197,"code":478,"language":199,"meta":200,"style":200},"        \u002F\u002F Initialize EGL\n        EGLint major, minor;\n        if (!eglInitialize(impl_->display_, &major, &minor)) {\n            std::cerr \u003C\u003C \"RenderContext: Failed to initialize EGL\" \u003C\u003C std::endl;\n            impl_->display_ = EGL_NO_DISPLAY;\n            return false;\n        }\n",[99,480,481,486,491,496,501,506,511],{"__ignoreMap":200},[135,482,483],{"class":205,"line":206},[135,484,485],{},"        \u002F\u002F Initialize EGL\n",[135,487,488],{"class":205,"line":212},[135,489,490],{},"        EGLint major, minor;\n",[135,492,493],{"class":205,"line":219},[135,494,495],{},"        if (!eglInitialize(impl_->display_, &major, &minor)) {\n",[135,497,498],{"class":205,"line":225},[135,499,500],{},"            std::cerr \u003C\u003C \"RenderContext: Failed to initialize EGL\" \u003C\u003C std::endl;\n",[135,502,503],{"class":205,"line":230},[135,504,505],{},"            impl_->display_ = EGL_NO_DISPLAY;\n",[135,507,508],{"class":205,"line":236},[135,509,510],{},"            return false;\n",[135,512,513],{"class":205,"line":241},[135,514,515],{},"        }\n",[10,517,518],{},"如果这里系统环境存在了问题，比如说机器显卡不支持D3D11，库文件确实，或者某些沙箱环境下的权限问题（无法访问显卡硬件）。",[168,520,521],{"id":521},"配置绘图表面",[10,523,524],{},"然后我们去配置绘图表面",[195,526,528],{"className":197,"code":527,"language":199,"meta":200,"style":200},"        \u002F\u002F Configure surface attributes\n        const EGLint configAttributes[] = {\n            EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8,\n            EGL_DEPTH_SIZE, 8, EGL_STENCIL_SIZE, 8,\n            EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT,\n            EGL_NONE\n\n        };\n",[99,529,530,535,540,545,550,555,559,563],{"__ignoreMap":200},[135,531,532],{"class":205,"line":206},[135,533,534],{},"        \u002F\u002F Configure surface attributes\n",[135,536,537],{"class":205,"line":212},[135,538,539],{},"        const EGLint configAttributes[] = {\n",[135,541,542],{"class":205,"line":219},[135,543,544],{},"            EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8,\n",[135,546,547],{"class":205,"line":225},[135,548,549],{},"            EGL_DEPTH_SIZE, 8, EGL_STENCIL_SIZE, 8,\n",[135,551,552],{"class":205,"line":230},[135,553,554],{},"            EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT,\n",[135,556,557],{"class":205,"line":236},[135,558,333],{},[135,560,561],{"class":205,"line":241},[135,562,216],{"emptyLinePlaceholder":215},[135,564,565],{"class":205,"line":247},[135,566,343],{},[10,568,569,570,573,574,577,580,581,584],{},"我们在这里定义了一组约束条件，从开始分别是RGBA8888格式的颜色通道\n辅助缓冲区（",[27,571,572],{},"深度缓冲区，用于判断物体的前后遮挡关系（Z-buffer）。虽然 UI 通常是 2D 的，但如果你想做 3D 效果或复杂的层级叠加，它非常有用","）",[575,576],"br",{},[27,578,579],{},"渲染能力","（这里配置的必须要运行OpenGL ES3.0，如果只选了一个ES2.0则后面的",[99,582,583],{},"eglCreateContext"," 就会报错。）",[10,586,587],{},"配置筛选好匹配环节：",[195,589,591],{"className":197,"code":590,"language":199,"meta":200,"style":200}," \u002F\u002F Choose EGL configuration\n\n        EGLint numConfigs;\n        if (!eglChooseConfig(impl_->display_, configAttributes, &impl_->config_, 1, &numConfigs) || numConfigs == 0) {\n            std::cerr \u003C\u003C \"RenderContext: Failed to choose EGL config\" \u003C\u003C std::endl;\n            eglTerminate(impl_->display_);\n            impl_->display_ = EGL_NO_DISPLAY;\n            return false;\n        }\n",[99,592,593,598,602,607,612,617,622,626,630],{"__ignoreMap":200},[135,594,595],{"class":205,"line":206},[135,596,597],{}," \u002F\u002F Choose EGL configuration\n",[135,599,600],{"class":205,"line":212},[135,601,216],{"emptyLinePlaceholder":215},[135,603,604],{"class":205,"line":219},[135,605,606],{},"        EGLint numConfigs;\n",[135,608,609],{"class":205,"line":225},[135,610,611],{},"        if (!eglChooseConfig(impl_->display_, configAttributes, &impl_->config_, 1, &numConfigs) || numConfigs == 0) {\n",[135,613,614],{"class":205,"line":230},[135,615,616],{},"            std::cerr \u003C\u003C \"RenderContext: Failed to choose EGL config\" \u003C\u003C std::endl;\n",[135,618,619],{"class":205,"line":236},[135,620,621],{},"            eglTerminate(impl_->display_);\n",[135,623,624],{"class":205,"line":241},[135,625,505],{},[135,627,628],{"class":205,"line":247},[135,629,510],{},[135,631,632],{"class":205,"line":252},[135,633,515],{},[10,635,636,642,643,649,650,653,654,657,658,664,665,667,668,674,675,678],{},[27,637,638,641],{},[99,639,640],{},"configAttributes","（查询条件）","：传入你刚才定义的 RGBA8888、Stencil 8 等硬性指标，",[27,644,645,648],{},[99,646,647],{},"&impl_->config_","（接收缓冲区）","：这是一个输出参数。如果",[27,651,652],{},"找到了匹配的配置","，驱动会把这个配置的",[27,655,656],{},"句柄","存入这里。\n",[27,659,660,663],{},[99,661,662],{},"1","（最大获取数量）","：你告诉 EGL：“我只需要最匹配的那 ",[27,666,662],{}," 个。” 实际上显卡可能返回几十个符合条件的配置，但通常拿第一个（排名最高、性能最稳定）就够了。\n",[27,669,670,673],{},[99,671,672],{},"&numConfigs","（实际找到的数量）","：这也是一个",[27,676,677],{},"输出参数","。它会告诉你显卡里到底有多少个配置符合你的要求。![[Pasted image 20260101005902.png]]",[10,680,681,682,685],{},"下面",[27,683,684],{},"设置dspplay_ = EGL_NO_DISPLAY是为了清理资源","，如果不设置的话，可能外部逻辑会误以为初始化已经部分成功，会导致后续的调用失败。",[168,687,688],{"id":688},"两个上下文的配置",[10,690,691],{},"绑定上下文",[195,693,695],{"className":197,"code":694,"language":199,"meta":200,"style":200},"        \u002F\u002F Bind OpenGL ES API\n\n        if (!eglBindAPI(EGL_OPENGL_ES_API)) {\n\n            std::cerr \u003C\u003C \"RenderContext: Failed to bind OpenGL ES API\" \u003C\u003C std::endl;\n\n            eglTerminate(impl_->display_);\n\n            impl_->display_ = EGL_NO_DISPLAY;\n\n            return false;\n\n        }\n",[99,696,697,702,706,711,715,720,724,728,732,736,740,744,748],{"__ignoreMap":200},[135,698,699],{"class":205,"line":206},[135,700,701],{},"        \u002F\u002F Bind OpenGL ES API\n",[135,703,704],{"class":205,"line":212},[135,705,216],{"emptyLinePlaceholder":215},[135,707,708],{"class":205,"line":219},[135,709,710],{},"        if (!eglBindAPI(EGL_OPENGL_ES_API)) {\n",[135,712,713],{"class":205,"line":225},[135,714,216],{"emptyLinePlaceholder":215},[135,716,717],{"class":205,"line":230},[135,718,719],{},"            std::cerr \u003C\u003C \"RenderContext: Failed to bind OpenGL ES API\" \u003C\u003C std::endl;\n",[135,721,722],{"class":205,"line":236},[135,723,216],{"emptyLinePlaceholder":215},[135,725,726],{"class":205,"line":241},[135,727,621],{},[135,729,730],{"class":205,"line":247},[135,731,216],{"emptyLinePlaceholder":215},[135,733,734],{"class":205,"line":252},[135,735,505],{},[135,737,738],{"class":205,"line":258},[135,739,216],{"emptyLinePlaceholder":215},[135,741,742],{"class":205,"line":263},[135,743,510],{},[135,745,746],{"class":205,"line":340},[135,747,216],{"emptyLinePlaceholder":215},[135,749,751],{"class":205,"line":750},13,[135,752,515],{},[10,754,755,756,759,760,763,764,84],{},"在 EGL 的流程中，",[99,757,758],{},"eglBindAPI"," 是一个经常被开发者忽略但在",[27,761,762],{},"多 API 架构","中至关重要的“声明”步骤。\n这一步的作用是",[27,765,766],{},"明确告知 EGL 状态机：接下来的所有操作（尤其是创建 Context）都是针对哪种图形 API 的",[10,768,769],{},"我们在下面创建一个EGL上下文，设置使用的opengl es版本为3.0：",[195,771,774],{"className":772,"code":773,"language":359},[357],"        \u002F\u002F Create EGL context\n\n        const EGLint contextAttributes[] = {\n\n            EGL_CONTEXT_CLIENT_VERSION, 3, \u002F\u002F 使用 OpenGL ES 3.0\n\n            EGL_NONE\n\n        };\n",[99,775,773],{"__ignoreMap":200},[10,777,778],{},"通过context_  赋值，eglCreateContext(impl_->display_, impl_->config_, EGL_NO_CONTEXT, contextAttributes); 来接受它的返回值。",[10,780,781],{},"如果上下文的创建也没有问题，我们就可以开始进行上下文的激活，eglMakeCurrent(display, draw_surface, read_surface, context)\n![[Pasted image 20260101012056.png]]",[10,783,784,785,84],{},"这里我们将EGL上下文设置成了当前现场的活动上下文，注意这里使用了EGL_NO_SURFACE，因为此时没有窗口，窗口是由其他对象创建的额，而创建skia上下文就只需要激活上下文而不需要绘制表面，",[27,786,787],{},"我们后续在把它绑定到surface",[10,789,790],{},"有了上下文，我们需要创建GL接口，通过GL接口我们可以连接EGL和skia，这样skia就可以去调用opengl es的函数了。",[10,792,793],{},"GrGLMakeAssembledInterface 的作用，这是 Skia 提供的函数，用于创建 GL 接口：",[49,795,796,799],{},[52,797,798],{},"输入：一个函数指针获取器（lambda）",[52,800,801],{},"输出：GL 接口对象（包含所有 OpenGL 函数指针）",[10,803,804],{},"![[Pasted image 20260101012355.png]]",[10,806,807],{},"skia可以调用opengl函数之后，我们就可以去创建skia gpu渲染上下文，也就是使用之前创建的GL接口去创建skia的gpu渲染上下文，",[195,809,811],{"className":197,"code":810,"language":199,"meta":200,"style":200},"        impl_->skiaContext_ = GrDirectContexts::MakeGL(interface);\n\n        if (!impl_->skiaContext_) {\n\n            std::cerr \u003C\u003C \"RenderContext: Failed to create Skia GL context\" \u003C\u003C std::endl;\n\n            return false;\n\n        }\n",[99,812,813,818,822,827,831,836,840,844,848],{"__ignoreMap":200},[135,814,815],{"class":205,"line":206},[135,816,817],{},"        impl_->skiaContext_ = GrDirectContexts::MakeGL(interface);\n",[135,819,820],{"class":205,"line":212},[135,821,216],{"emptyLinePlaceholder":215},[135,823,824],{"class":205,"line":219},[135,825,826],{},"        if (!impl_->skiaContext_) {\n",[135,828,829],{"class":205,"line":225},[135,830,216],{"emptyLinePlaceholder":215},[135,832,833],{"class":205,"line":230},[135,834,835],{},"            std::cerr \u003C\u003C \"RenderContext: Failed to create Skia GL context\" \u003C\u003C std::endl;\n",[135,837,838],{"class":205,"line":236},[135,839,216],{"emptyLinePlaceholder":215},[135,841,842],{"class":205,"line":241},[135,843,510],{},[135,845,846],{"class":205,"line":247},[135,847,216],{"emptyLinePlaceholder":215},[135,849,850],{"class":205,"line":252},[135,851,515],{},[10,853,854],{},"GrDirectContext 是 Skia 的 GPU 渲染上下文，可以管理GPU资源（纹理和缓冲区），可以执行GPU渲染命令，可以提供skia的GPU加速渲染能力。",[10,856,857],{},"以上的完整初始化链条：\nEGL Context (OpenGL ES 3.0 上下文)\n↓\nGL Interface (OpenGL 函数指针集合)\n↓\nSkia Context (Skia GPU 渲染上下文) ← 你问的这段\n↓\n可以创建 Skia Surface 进行绘制",[10,859,860],{},"数据流和关系，从 EGL 到 Skia 的完整流程",[862,863,864,867,870,873,876,879],"ol",{},[52,865,866],{},"EGL Display (第 41 行)\n↓",[52,868,869],{},"EGL Context (第 95 行)\n↓",[52,871,872],{},"激活 Context (第 106 行)\n↓",[52,874,875],{},"GL Interface (第 118 行) ← 提供 OpenGL 函数指针\n↓",[52,877,878],{},"Skia Context (第 127 行) ← 你问的这段\n↓",[52,880,881],{},"可以使用 Skia 进行 GPU 渲染",[71,883,884],{"id":884},"画布的初始化",[10,886,887],{},"我们有了上下文的初始化之后，就可以去创建一个具体的画布，并通过GLFW创建的窗口来绑定，这样就可以在窗口上来绘制东西。",[10,889,890],{},"我们和之前的设计思路是一样的，我会在内部实现一个结构体impl：",[195,892,894],{"className":197,"code":893,"language":199,"meta":200,"style":200},"struct RenderSurface::Impl {\n\n    boost::shared_ptr\u003CRenderContext> context_;\n\n    EGLSurface eglSurface_ = EGL_NO_SURFACE;\n\n    sk_sp\u003CSkSurface> skSurface_;\n\n    int width_ = 0;\n\n    int height_ = 0;\n\n    bool initialized_ = false;\n\n    SkCanvas* currentCanvas_ = nullptr;\n\n};\n",[99,895,896,901,905,910,914,919,923,928,932,937,941,946,950,955,960,966,971],{"__ignoreMap":200},[135,897,898],{"class":205,"line":206},[135,899,900],{},"struct RenderSurface::Impl {\n",[135,902,903],{"class":205,"line":212},[135,904,216],{"emptyLinePlaceholder":215},[135,906,907],{"class":205,"line":219},[135,908,909],{},"    boost::shared_ptr\u003CRenderContext> context_;\n",[135,911,912],{"class":205,"line":225},[135,913,216],{"emptyLinePlaceholder":215},[135,915,916],{"class":205,"line":230},[135,917,918],{},"    EGLSurface eglSurface_ = EGL_NO_SURFACE;\n",[135,920,921],{"class":205,"line":236},[135,922,216],{"emptyLinePlaceholder":215},[135,924,925],{"class":205,"line":241},[135,926,927],{},"    sk_sp\u003CSkSurface> skSurface_;\n",[135,929,930],{"class":205,"line":247},[135,931,216],{"emptyLinePlaceholder":215},[135,933,934],{"class":205,"line":252},[135,935,936],{},"    int width_ = 0;\n",[135,938,939],{"class":205,"line":258},[135,940,216],{"emptyLinePlaceholder":215},[135,942,943],{"class":205,"line":263},[135,944,945],{},"    int height_ = 0;\n",[135,947,948],{"class":205,"line":340},[135,949,216],{"emptyLinePlaceholder":215},[135,951,952],{"class":205,"line":750},[135,953,954],{},"    bool initialized_ = false;\n",[135,956,958],{"class":205,"line":957},14,[135,959,216],{"emptyLinePlaceholder":215},[135,961,963],{"class":205,"line":962},15,[135,964,965],{},"    SkCanvas* currentCanvas_ = nullptr;\n",[135,967,969],{"class":205,"line":968},16,[135,970,216],{"emptyLinePlaceholder":215},[135,972,974],{"class":205,"line":973},17,[135,975,976],{},"};\n",[10,978,979],{},"分别代表了渲染环境的持有，context_持有了对于刚才创建的上下文的引用，因为多个窗口可能共享一个上下文，因此这里使用共享指针，只要还存在一个窗口，那么渲染环境就不会被销毁。",[10,981,982],{},"eglSurface是一个GPU窗口表面，这就是EGL架构图中的surface(s)，他表示了显卡驱动管理的特定窗口的前后台缓冲区。",[10,984,985,986,989,990,993],{},"skSurface_是skia的绘图表面，提供了高级的绘图接口，可以用于实现路径绘画、文字和图片等。用 ",[99,987,988],{},"sk_sp","（Skia 的智能指针）来",[27,991,992],{},"管理生命周期","。每当窗口缩放（Resize）时，这个对象会被重新创建以匹配新的像素尺寸。",[10,995,996],{},"我们在画布这里需要从渲染环境那里去接受核心的GPU管理句柄：",[195,998,1000],{"className":197,"code":999,"language":199,"meta":200,"style":200},"    \u002F\u002F 获取 EGL 原生句柄\n\n    auto nativeHandles = impl_->context_->GetNativeHandles();\n\n    EGLDisplay display = static_cast\u003CEGLDisplay>(nativeHandles.display);\n\n    EGLConfig config = static_cast\u003CEGLConfig>(nativeHandles.config);\n\n    if (display == EGL_NO_DISPLAY || config == nullptr) {\n\n        std::cerr \u003C\u003C \"RenderSurface: Invalid EGL handles from RenderContext\" \u003C\u003C std::endl;\n\n        return false;\n\n    }\n",[99,1001,1002,1007,1011,1016,1020,1025,1029,1034,1038,1043,1047,1052,1056,1061,1065],{"__ignoreMap":200},[135,1003,1004],{"class":205,"line":206},[135,1005,1006],{},"    \u002F\u002F 获取 EGL 原生句柄\n",[135,1008,1009],{"class":205,"line":212},[135,1010,216],{"emptyLinePlaceholder":215},[135,1012,1013],{"class":205,"line":219},[135,1014,1015],{},"    auto nativeHandles = impl_->context_->GetNativeHandles();\n",[135,1017,1018],{"class":205,"line":225},[135,1019,216],{"emptyLinePlaceholder":215},[135,1021,1022],{"class":205,"line":230},[135,1023,1024],{},"    EGLDisplay display = static_cast\u003CEGLDisplay>(nativeHandles.display);\n",[135,1026,1027],{"class":205,"line":236},[135,1028,216],{"emptyLinePlaceholder":215},[135,1030,1031],{"class":205,"line":241},[135,1032,1033],{},"    EGLConfig config = static_cast\u003CEGLConfig>(nativeHandles.config);\n",[135,1035,1036],{"class":205,"line":247},[135,1037,216],{"emptyLinePlaceholder":215},[135,1039,1040],{"class":205,"line":252},[135,1041,1042],{},"    if (display == EGL_NO_DISPLAY || config == nullptr) {\n",[135,1044,1045],{"class":205,"line":258},[135,1046,216],{"emptyLinePlaceholder":215},[135,1048,1049],{"class":205,"line":263},[135,1050,1051],{},"        std::cerr \u003C\u003C \"RenderSurface: Invalid EGL handles from RenderContext\" \u003C\u003C std::endl;\n",[135,1053,1054],{"class":205,"line":340},[135,1055,216],{"emptyLinePlaceholder":215},[135,1057,1058],{"class":205,"line":750},[135,1059,1060],{},"        return false;\n",[135,1062,1063],{"class":205,"line":957},[135,1064,216],{"emptyLinePlaceholder":215},[135,1066,1067],{"class":205,"line":962},[135,1068,1069],{},"    }\n",[10,1071,1072],{},"我们通过了之前的方法建立了GPU通道，我们这里的Surface负责将它应用到特定的窗口上，我们通过调用",[195,1074,1076],{"className":197,"code":1075,"language":199,"meta":200,"style":200},"    RenderContext::NativeHandles RenderContext::GetNativeHandles() const {\n        RenderContext::NativeHandles handles;\n        handles.display = nullptr;\n        handles.context = nullptr;\n        handles.config = nullptr;\n        if (impl_) {\n            handles.display = impl_->display_;\n            handles.context = impl_->context_;\n            handles.config = impl_->config_;\n        }\n        return handles;\n    }\n",[99,1077,1078,1083,1088,1093,1098,1103,1108,1113,1118,1123,1127,1132],{"__ignoreMap":200},[135,1079,1080],{"class":205,"line":206},[135,1081,1082],{},"    RenderContext::NativeHandles RenderContext::GetNativeHandles() const {\n",[135,1084,1085],{"class":205,"line":212},[135,1086,1087],{},"        RenderContext::NativeHandles handles;\n",[135,1089,1090],{"class":205,"line":219},[135,1091,1092],{},"        handles.display = nullptr;\n",[135,1094,1095],{"class":205,"line":225},[135,1096,1097],{},"        handles.context = nullptr;\n",[135,1099,1100],{"class":205,"line":230},[135,1101,1102],{},"        handles.config = nullptr;\n",[135,1104,1105],{"class":205,"line":236},[135,1106,1107],{},"        if (impl_) {\n",[135,1109,1110],{"class":205,"line":241},[135,1111,1112],{},"            handles.display = impl_->display_;\n",[135,1114,1115],{"class":205,"line":247},[135,1116,1117],{},"            handles.context = impl_->context_;\n",[135,1119,1120],{"class":205,"line":252},[135,1121,1122],{},"            handles.config = impl_->config_;\n",[135,1124,1125],{"class":205,"line":258},[135,1126,515],{},[135,1128,1129],{"class":205,"line":263},[135,1130,1131],{},"        return handles;\n",[135,1133,1134],{"class":205,"line":340},[135,1135,1069],{},[10,1137,1138],{},"获取了已经初始化好了的EGLdisplay和EGLconfig。为了保持接口的通用性，可能句柄包装成了void*，我们将其还原为EGL的标准类型，所以用了类型转换static_cast。",[10,1140,1141],{},"我们接下来去创建surface，将窗口句柄给转换为EGL表面，",[195,1143,1145],{"className":197,"code":1144,"language":199,"meta":200,"style":200},"    \u002F\u002F 创建 EGLSurface（将窗口句柄转换为 EGL 表面）\n    EGLint surfaceAttributes[] = {\n        EGL_NONE\n    };\n    \u002F\u002F 创建 EGLSurface\n    impl_->eglSurface_ = eglCreateWindowSurface(display, config, windowHandle, surfaceAttributes);\n    if (impl_->eglSurface_ == EGL_NO_SURFACE) {\n        EGLint error = eglGetError();\n        std::cerr \u003C\u003C \"RenderSurface: Failed to create EGL surface, error: 0x\"\n                  \u003C\u003C std::hex \u003C\u003C error \u003C\u003C std::dec \u003C\u003C std::endl;\n        return false;\n    }\n",[99,1146,1147,1152,1157,1162,1166,1171,1176,1181,1186,1191,1196,1200],{"__ignoreMap":200},[135,1148,1149],{"class":205,"line":206},[135,1150,1151],{},"    \u002F\u002F 创建 EGLSurface（将窗口句柄转换为 EGL 表面）\n",[135,1153,1154],{"class":205,"line":212},[135,1155,1156],{},"    EGLint surfaceAttributes[] = {\n",[135,1158,1159],{"class":205,"line":219},[135,1160,1161],{},"        EGL_NONE\n",[135,1163,1164],{"class":205,"line":225},[135,1165,266],{},[135,1167,1168],{"class":205,"line":230},[135,1169,1170],{},"    \u002F\u002F 创建 EGLSurface\n",[135,1172,1173],{"class":205,"line":236},[135,1174,1175],{},"    impl_->eglSurface_ = eglCreateWindowSurface(display, config, windowHandle, surfaceAttributes);\n",[135,1177,1178],{"class":205,"line":241},[135,1179,1180],{},"    if (impl_->eglSurface_ == EGL_NO_SURFACE) {\n",[135,1182,1183],{"class":205,"line":247},[135,1184,1185],{},"        EGLint error = eglGetError();\n",[135,1187,1188],{"class":205,"line":252},[135,1189,1190],{},"        std::cerr \u003C\u003C \"RenderSurface: Failed to create EGL surface, error: 0x\"\n",[135,1192,1193],{"class":205,"line":258},[135,1194,1195],{},"                  \u003C\u003C std::hex \u003C\u003C error \u003C\u003C std::dec \u003C\u003C std::endl;\n",[135,1197,1198],{"class":205,"line":263},[135,1199,1060],{},[135,1201,1202],{"class":205,"line":340},[135,1203,1069],{},[10,1205,1206],{},"display就是刚才初始化了的gpu连接，config就是刚才的画布规格，windowHandle就是我们拿到的窗口句柄，surfaceAttributes我们设置EGL_NONE,会使用默认行为。",[10,1208,1209],{},"如果使用高级用法，1可以在这里制定渲染缓冲区行为，比如指向单缓冲还是双缓冲。",[10,1211,1212],{},"再确认了物理画布之后，我们需要建立线程 ---》 上下文 ----》 表面的绑定关系，将这一套环境正式交给skia图像引擎。",[195,1214,1216],{"className":197,"code":1215,"language":199,"meta":200,"style":200},"    \u002F\u002F 使 EGL 上下文成为当前上下文\n    EGLContext context = static_cast\u003CEGLContext>(nativeHandles.context);\n    if (!eglMakeCurrent(display, impl_->eglSurface_, impl_->eglSurface_, context)) {\n        EGLint error = eglGetError();\n        std::cerr \u003C\u003C \"RenderSurface: Failed to make EGL context current, error: 0x\"\n                  \u003C\u003C std::hex \u003C\u003C error \u003C\u003C std::dec \u003C\u003C std::endl;\n        eglDestroySurface(display, impl_->eglSurface_);\n        impl_->eglSurface_ = EGL_NO_SURFACE;\n        return false;\n\n    }\n\n    \u002F\u002F 创建 Skia 表面（SkSurface）\n    GrDirectContext* skiaContext = impl_->context_->GetSkiaContext();\n    if (!skiaContext) {\n        std::cerr \u003C\u003C \"RenderSurface: Skia context is null\" \u003C\u003C std::endl;\n        eglDestroySurface(display, impl_->eglSurface_);\n        impl_->eglSurface_ = EGL_NO_SURFACE;\n        return false;\n    }\n",[99,1217,1218,1223,1228,1233,1237,1242,1246,1251,1256,1260,1264,1268,1272,1277,1282,1287,1292,1296,1301,1306],{"__ignoreMap":200},[135,1219,1220],{"class":205,"line":206},[135,1221,1222],{},"    \u002F\u002F 使 EGL 上下文成为当前上下文\n",[135,1224,1225],{"class":205,"line":212},[135,1226,1227],{},"    EGLContext context = static_cast\u003CEGLContext>(nativeHandles.context);\n",[135,1229,1230],{"class":205,"line":219},[135,1231,1232],{},"    if (!eglMakeCurrent(display, impl_->eglSurface_, impl_->eglSurface_, context)) {\n",[135,1234,1235],{"class":205,"line":225},[135,1236,1185],{},[135,1238,1239],{"class":205,"line":230},[135,1240,1241],{},"        std::cerr \u003C\u003C \"RenderSurface: Failed to make EGL context current, error: 0x\"\n",[135,1243,1244],{"class":205,"line":236},[135,1245,1195],{},[135,1247,1248],{"class":205,"line":241},[135,1249,1250],{},"        eglDestroySurface(display, impl_->eglSurface_);\n",[135,1252,1253],{"class":205,"line":247},[135,1254,1255],{},"        impl_->eglSurface_ = EGL_NO_SURFACE;\n",[135,1257,1258],{"class":205,"line":252},[135,1259,1060],{},[135,1261,1262],{"class":205,"line":258},[135,1263,216],{"emptyLinePlaceholder":215},[135,1265,1266],{"class":205,"line":263},[135,1267,1069],{},[135,1269,1270],{"class":205,"line":340},[135,1271,216],{"emptyLinePlaceholder":215},[135,1273,1274],{"class":205,"line":750},[135,1275,1276],{},"    \u002F\u002F 创建 Skia 表面（SkSurface）\n",[135,1278,1279],{"class":205,"line":957},[135,1280,1281],{},"    GrDirectContext* skiaContext = impl_->context_->GetSkiaContext();\n",[135,1283,1284],{"class":205,"line":962},[135,1285,1286],{},"    if (!skiaContext) {\n",[135,1288,1289],{"class":205,"line":968},[135,1290,1291],{},"        std::cerr \u003C\u003C \"RenderSurface: Skia context is null\" \u003C\u003C std::endl;\n",[135,1293,1294],{"class":205,"line":973},[135,1295,1250],{},[135,1297,1299],{"class":205,"line":1298},18,[135,1300,1255],{},[135,1302,1304],{"class":205,"line":1303},19,[135,1305,1060],{},[135,1307,1309],{"class":205,"line":1308},20,[135,1310,1069],{},[10,1312,1313,1316,1317,1320,1321,1324,1325,1328],{},[99,1314,1315],{},"GrDirectContext"," 是 Skia 指令发送给 GPU 的唯一通道，如果绑定失败或 Skia 上下文无效，代码会立即销毁刚刚创建的 ",[99,1318,1319],{},"eglSurface_","。如果不手动调用 ",[99,1322,1323],{},"eglDestroySurface","，即使 ",[99,1326,1327],{},"RenderSurface"," 对象被销毁，这块显存也可能一直被驱动占用，导致程序运行时间越长，系统显存占用越高。",[10,1330,1331],{},"最后我们需要去定义硬件帧的缓冲句柄：",[195,1333,1335],{"className":197,"code":1334,"language":199,"meta":200,"style":200},"GrGLFramebufferInfo framebufferInfo;\nframebufferInfo.fFBOID = 0;\n",[99,1336,1337,1342],{"__ignoreMap":200},[135,1338,1339],{"class":205,"line":206},[135,1340,1341],{},"GrGLFramebufferInfo framebufferInfo;\n",[135,1343,1344],{"class":205,"line":212},[135,1345,1346],{},"framebufferInfo.fFBOID = 0;\n",[10,1348,1349,1350,1353],{},"在 OpenGL\u002FEGL 标准中，ID 为 ",[99,1351,1352],{},"0"," 的帧缓冲，这是一个特殊的存在，它代表“默认的后台缓冲区”**——即直接连接到操作系统的窗口表面的那块内存。",[10,1355,1356],{},"这里我们设置为0让skia可以在这里绘制，这样绘制的东西会显示在窗口上。",[10,1358,1359],{},"我们去构建渲染目标：",[195,1361,1363],{"className":197,"code":1362,"language":199,"meta":200,"style":200},"GrBackendRenderTarget backendRenderTarget = GrBackendRenderTargets::MakeGL(\n    impl_->width_, impl_->height_, 0, 0, framebufferInfo\n);\n",[99,1364,1365,1370,1375],{"__ignoreMap":200},[135,1366,1367],{"class":205,"line":206},[135,1368,1369],{},"GrBackendRenderTarget backendRenderTarget = GrBackendRenderTargets::MakeGL(\n",[135,1371,1372],{"class":205,"line":212},[135,1373,1374],{},"    impl_->width_, impl_->height_, 0, 0, framebufferInfo\n",[135,1376,1377],{"class":205,"line":219},[135,1378,1379],{},");\n",[10,1381,1382],{},"这里是skia对外部GPU资源的抽象描述符号，这里我们组合了物理像素尺寸和刚才确定的FBO信息，这样显存就可以去接受skia的指令了。",[195,1384,1386],{"className":197,"code":1385,"language":199,"meta":200,"style":200},"impl_->skSurface_ = SkSurfaces::WrapBackendRenderTarget(\n    recordingContext,\n    backendRenderTarget,\n    kBottomLeft_GrSurfaceOrigin, \u002F\u002F 坐标系适配\n    kRGBA_8888_SkColorType,      \u002F\u002F 像素类型\n    nullptr, nullptr\n);\n",[99,1387,1388,1393,1398,1403,1408,1413,1418],{"__ignoreMap":200},[135,1389,1390],{"class":205,"line":206},[135,1391,1392],{},"impl_->skSurface_ = SkSurfaces::WrapBackendRenderTarget(\n",[135,1394,1395],{"class":205,"line":212},[135,1396,1397],{},"    recordingContext,\n",[135,1399,1400],{"class":205,"line":219},[135,1401,1402],{},"    backendRenderTarget,\n",[135,1404,1405],{"class":205,"line":225},[135,1406,1407],{},"    kBottomLeft_GrSurfaceOrigin, \u002F\u002F 坐标系适配\n",[135,1409,1410],{"class":205,"line":230},[135,1411,1412],{},"    kRGBA_8888_SkColorType,      \u002F\u002F 像素类型\n",[135,1414,1415],{"class":205,"line":236},[135,1416,1417],{},"    nullptr, nullptr\n",[135,1419,1420],{"class":205,"line":241},[135,1421,1379],{},[10,1423,1424],{},"因为OpenGL的坐标点在左下角，skia通过这个参数就可以自动完成坐标系的镜像反转，让我们写UI逻辑更加符合直觉，这里调用的就是skia的顶级api，他不会分配新的显存，而是会接管EGL的缓冲区。 一旦skSurface_创建成功了，我们就可以拿到它的SKCanvas，从而开启高性能的绘图。",[1426,1427,1428],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":200,"searchDepth":212,"depth":212,"links":1430},[1431,1432,1433,1439],{"id":73,"depth":212,"text":74},{"id":112,"depth":212,"text":112},{"id":166,"depth":212,"text":166,"children":1434},[1435,1436,1437,1438],{"id":170,"depth":219,"text":171},{"id":281,"depth":219,"text":282},{"id":521,"depth":219,"text":521},{"id":688,"depth":219,"text":688},{"id":884,"depth":212,"text":884},"OpenGL 是一种跨平台的图形 API，用于为 3D 图形处理硬件指定标准的软件接口。OpenGL ES 是 OpenGL 规范的子集，适用于嵌入式设备。而EGL 是渲染 API（如 OpenGL ES）和原生窗口系统之间的接口.通常来说，OpenGL 是一个操作 GPU 的 API，它通过驱动向 GPU 发送相关指令，控制图形渲染管线状态机的运行状态，但是当涉及到与本地窗口系统进行交互时，就需要这么一个中间层，且它最好是与平台无关的,","md",{},"\u002Fcpp\u002Fegl和skia",{"description":1440},"cpp\u002FEGL和Skia","Uz7b7IGseuXi7_xaRz2TS26lFf7OfI_NAtRGrNhLfNo",1791042462581]