在PPAPI中使用Chromium的3D图形接口一文中我们介绍了怎么使用PPB_Graphics3D接口,提供了一个简单示例,单机鼠标可以变换插件颜色。
foruok原创,如需转载请关注foruok的微信订阅号“程序视界”联系foruok。
PPB_Graphics3D是Chromium暴露给PPAPI的3D图形接口,类似衔接Open GL和本地窗口系统的EGL。我们使用PPB_Graphics3D的Create方法来创建context,然后使用PPB_Instance的BindGraphics将得到的OpenGL Context和插件实例绑定,再接下来,就可以使用OpenGL来绘图了。在这个过程中,PPB_Graphics3D替代了EGL。
Chromium使用的应该是WebGL,也就是OpenGL ES 2.0 for the Web,它暴露给PPAPI的gl接口是C结构体加函数指针的形式,有PPB_OpenGLES2、PPB_OpenGLES2FramebufferBlit、PPB_OpenGLES2ChromiumEnableFeature等,搜索ppb_opengles2*.h即可查看对应的接口定义。
关于OpenGL ES,可以看这里:https://www.khronos.org/opengles/。
PPAPI中使用OpenGL ES2
在PPAPI中使用Chromium的3D图形接口一文中我们提到了glInitializePPAPI和glSetCurrentContextPPAPI两个方法,解释一下。
- glInitializePPAPI
glInitializePPAPI做的事情就是把浏览器暴露给PPAPI的各种gl相关的接口都拿到(通过PPB_GetInterface),保存在全局变量中(参看gl2ext_ppapi.c),后续我们使用glXXX(参看gles2.c)函数时,实际上是通过保存下来的接口(结构体+函数指针),调用函数指针来实现的。我们用到的glXXX是宏定义,看起来和OpenGL的API一致,用起来方便。
我在ppapi_hello_gles.c的PPP_InitializeModule方法中添加了下面的代码:
if (GL_TRUE != glInitializePPAPI(get_browser_interface))
return -1;
上面的代码初始化PPAPI相关的gl接口。
- glSetCurrentContextPPAPI
PPB_Graphics3D的Create方法创建一个图形上下文,OpenGL就用它绘图。glSetCurrentContextPPAPI方法(参看gl2ext_ppapi.c)需要的参数就是PPB_Graphics3D接口Create出来的那个context。
ppapi_hello_gles.c中的MakeAndBindGraphics3D函数创建了Graphics3D context,并调用glSetCurrentContextPPAPI来传递给封装gl C接口的模块:
PP_Resource MakeAndBindGraphics3D(PP_Instance instance,
const struct PP_Size* size) {
PP_Resource graphics;
int32_t attribs[] = { PP_GRAPHICS3DATTRIB_WIDTH, 800,
PP_GRAPHICS3DATTRIB_HEIGHT, 800,
PP_GRAPHICS3DATTRIB_NONE };
graphics = g_graphics_3d_interface->Create(instance, 0, attribs);
if (!graphics)
return 0;
if (!g_instance_interface->BindGraphics(instance, graphics)) {
g_core_interface->ReleaseResource(graphics);
return 0;
}
glSetCurrentContextPPAPI(graphics);
return graphics;
}
好啦,我们对PPAPI中使用Chromium的3D图形接口一文做了一些补充说明,了解了在PPAPI中可以使用OpenGL ES 2.0的接口来进行3D绘图,这种方式比基于软件和共享内存的Graphics 2D接口效率要高,它利用了GPU加速。接下来看看怎么使用gl接口来绘制一个简单的三角形。
绘制三角形
修改了ppapi_hello_gles实例,添加了glTriangle.h和glTriangle.c两个文件,它们实现了三角形绘制。大部分代码是我从《OpenGL ES 2.0 Programming Guide》一书中摘出来的,针对PPAPI做了修改。
glTriangle.h如下:
#ifndef GLTRIANGLE_DRAW_H
#define GLTRIANGLE_DRAW_H
#include "ppapi/lib/gl/include/GLES2/gl2.h"
GLboolean InitTriangle(void **userData);
void DrawTriangle(void *userData);
#endif
声明了两个函数,ppapi_hello_gles.c中会用到。
glTriangle.c如下:
/* Copyright (c) 2016 foruok. All rights reserved.
* 欢迎关注我的微信订阅号程序视界
* see: 《OpenGL ES 2.0 Programming Guide》
*/
#include "glTriangle.h"
#include <Windows.h>
#include "ppapi/lib/gl/include/GLES2/gl2.h"
#include "ppapi/lib/gl/gles2/gl2ext_ppapi.h"
#include "ppapi/lib/gl/include/GLES2/gl2ext.h"
#include <tchar.h>
typedef struct
{
GLuint programObject;
} UserData;
/*
* create a shader object, load the shader source, and compile the shader
*/
GLuint LoadShader(GLenum type, const char *shaderSource)
{
GLuint shader;
GLint compiled;
// create the shader object
shader = glCreateShader(type);
if (shader == 0)
return 0;
// load the shader source
glShaderSource(shader, 1, &shaderSource, NULL);
// compile the shader
glCompileShader(shader);
// check the compile status
glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
if (!compiled)
{
GLint infoLen = 0;
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen);
if (infoLen > 1)
{
char *infoLog = malloc(infoLen+1);
glGetShaderInfoLog(shader, infoLen, NULL, infoLog);
infoLog[infoLen] = 0;
OutputDebugStringA(infoLog);
free(infoLog);
}
glDeleteShader(shader);
return 0;
}
return shader;
}
/*
* Initialize the shader and program object
*/
GLboolean InitTriangle(void **userData)
{
GLbyte vShaderStr[] =
"attribute vec4 vPosition; \n"
"void main() \n"
"{ \n"
" gl_Position = vPosition; \n"
"} \n";
GLbyte fShaderStr[] =
"precision mediump float; \n"
"void main() \n"
"{ \n"
" gl_FragColor = vec4(1.0, 0.5, 0.0, 1.0); \n"
"} \n";
// load shaders
GLuint vertexShader = LoadShader(GL_VERTEX_SHADER, vShaderStr);
GLuint fragmentShader = LoadShader(GL_FRAGMENT_SHADER, fShaderStr);
// create the program object
GLuint programObject = glCreateProgram();
if (programObject == 0) return GL_FALSE;
glAttachShader(programObject, vertexShader);
glAttachShader(programObject, fragmentShader);
// blind vPosition to attribute 0
glBindAttribLocation(programObject, 0, "vPosition");
// link the program
glLinkProgram(programObject);
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
// check link status
GLint linked;
glGetProgramiv(programObject, GL_LINK_STATUS, &linked);
if (!linked)
{
GLint infoLen = 0;
glGetProgramiv(programObject, GL_INFO_LOG_LENGTH, &infoLen);
if (infoLen > 1)
{
char *infoLog = malloc(infoLen + 1);
glGetProgramInfoLog(programObject, infoLen, NULL, infoLog);
infoLog[infoLen] = 0;
OutputDebugStringA(infoLog);
free(infoLog);
}
glDeleteProgram(programObject);
return GL_FALSE;
}
UserData *data = *userData;
if (data == 0)
{
data = (UserData*)malloc(sizeof(UserData));
*userData = data;
}
data->programObject = programObject;
return GL_TRUE;
}
void DrawTriangle(void *userData)
{
OutputDebugString(_T("DrawTriagle\r\n"));
UserData *data = userData;
GLfloat vVertices[] = {
0.0f, 0.5f, 0.0f,
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f
};
// use the program object
glUseProgram(data->programObject);
// load the vertex data
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, vVertices);
glEnableVertexAttribArray(0);
glDrawArrays(GL_TRIANGLES, 0, 3);
}
glTriangle.c的三个函数都来自《OpenGL ES 2.0 Programming Guide》一书,现在最新的书应该是《OpenGL ES 3.0 Programming Guide》。详细的代码解说请参考该书,非常详尽、有条理,超级棒!
最后,我修改了ppapi_hello_gles.c,以便使用glTriangle的接口。新的ppapi_hello_gles.c内容如下:
/* Copyright (c) 2016 foruok. All rights reserved.
* 欢迎关注我的微信订阅号程序视界
*/
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <Windows.h>
#include <tchar.h>
#include "ppapi/c/pp_completion_callback.h"
#include "ppapi/c/pp_errors.h"
#include "ppapi/c/pp_instance.h"
#include "ppapi/c/pp_module.h"
#include "ppapi/c/pp_rect.h"
#include "ppapi/c/pp_var.h"
#include "ppapi/c/ppb.h"
#include "ppapi/c/ppb_core.h"
#include "ppapi/c/ppb_instance.h"
#include "ppapi/c/ppb_view.h"
#include "ppapi/c/ppp.h"
#include "ppapi/c/ppp_instance.h"
#include "ppapi/c/ppb_input_event.h"
#include "ppapi/c/ppp_input_event.h"
#include "ppapi/c/ppb_graphics_3d.h"
#include "ppapi/c/ppb_opengles2.h"
#include "ppapi/lib/gl/include/GLES2/gl2.h"
#include "ppapi/lib/gl/gles2/gl2ext_ppapi.h"
// foruok[1]
#include "glTriangle.h"
PPB_GetInterface g_get_browser_interface = NULL;
const PPB_Core* g_core_interface;
const PPB_Graphics3D* g_graphics_3d_interface;
const PPB_Instance* g_instance_interface;
const PPB_View* g_view_interface;
const PPB_InputEvent *g_input_interface;
const PPB_MouseInputEvent *g_mouse_interface;
/* PPP_Instance implementation -----------------------------------------------*/
typedef struct InstanceInfo {
PP_Instance pp_instance;
struct PP_Size last_size;
PP_Resource graphics;
// foruok[2]
void *user_data;
struct InstanceInfo* next;
} InstanceInfo;
/** Linked list of all live instances. */
struct InstanceInfo* all_instances = NULL;
/** Returns a refed resource corresponding to the created graphics 3d. */
PP_Resource MakeAndBindGraphics3D(PP_Instance instance,
const struct PP_Size* size) {
PP_Resource graphics;
int32_t attribs[] = { PP_GRAPHICS3DATTRIB_WIDTH, 800,
PP_GRAPHICS3DATTRIB_HEIGHT, 800,
PP_GRAPHICS3DATTRIB_NONE };
graphics = g_graphics_3d_interface->Create(instance, 0, attribs);
if (!graphics)
return 0;
if (!g_instance_interface->BindGraphics(instance, graphics)) {
g_core_interface->ReleaseResource(graphics);
return 0;
}
glSetCurrentContextPPAPI(graphics);
return graphics;
}
void ReinitializeGraphics3D(void *user_data, int32_t result)
{
InstanceInfo *inst = (InstanceInfo*)user_data;
inst->graphics = MakeAndBindGraphics3D(inst->pp_instance, &inst->last_size);
if (inst->graphics != 0)
{
// foruok[5]
if(inst->user_data)InitTriangle(inst->user_data);
OutputDebugString(_T("reinitialize graphics 3d context sucess\r\n"));
}
}
void FlushCompletionCallback(void* user_data, int32_t result) {
/* Don‘t need to do anything here. */
if (result == PP_ERROR_CONTEXT_LOST)
{
OutputDebugString(_T("PP_ERROR_CONTEXT_LOST"));
//reinitialize context
g_core_interface->CallOnMainThread(0, PP_MakeCompletionCallback(ReinitializeGraphics3D, user_data), 0);
}
}
unsigned int g_colors[4] = { 0xFF0000FF, 0xFFFF00FF, 0xFF00FFFF, 0xFF2AFE00 };
unsigned int g_color_index = 0;
#define GETA(clr) ((clr >> 24) & 0xFF)
#define GETR(clr) ((clr >> 16) & 0xFF)
#define GETG(clr) ((clr >> 8) & 0xFF)
#define GETB(clr) (clr & 0xFF)
void Repaint(struct InstanceInfo* instance, const struct PP_Size* size) {
/* Ensure the graphics 3d is ready. */
if (!instance->graphics) {
instance->graphics = MakeAndBindGraphics3D(instance->pp_instance, size);
if (!instance->graphics)
return;
// foruok[3]
if (GL_TRUE == InitTriangle(&instance->user_data))
OutputDebugString(_T("InitTriangle OK\r\n"));
}
g_color_index++;
if (g_color_index >= sizeof(g_colors) / sizeof(g_colors[0])) g_color_index = 0;
struct PP_CompletionCallback callback = {
FlushCompletionCallback, instance, PP_COMPLETIONCALLBACK_FLAG_NONE,
};
glViewport(0, 0, instance->last_size.width, instance->last_size.height);
glClearColor(GETR(g_colors[g_color_index]),
GETG(g_colors[g_color_index]),
GETB(g_colors[g_color_index]),
GETA(g_colors[g_color_index]));
glClear(GL_COLOR_BUFFER_BIT);
// foruok[4]
if(instance->user_data)DrawTriangle(instance->user_data);
g_graphics_3d_interface->SwapBuffers(instance->graphics, callback);
}
/** Returns the info for the given instance, or NULL if it‘s not found. */
struct InstanceInfo* FindInstance(PP_Instance instance) {
struct InstanceInfo* cur = all_instances;
while (cur) {
if (cur->pp_instance == instance)
return cur;
cur = cur->next;
}
return NULL;
}
PP_Bool Instance_DidCreate(PP_Instance instance,
uint32_t argc,
const char* argn[],
const char* argv[]) {
struct InstanceInfo* info =
(struct InstanceInfo*)calloc(1, sizeof(struct InstanceInfo));
info->pp_instance = instance;
/* Insert into linked list of live instances. */
info->next = all_instances;
all_instances = info;
g_input_interface->RequestInputEvents(instance, PP_INPUTEVENT_CLASS_MOUSE);
g_input_interface->RequestFilteringInputEvents(instance, PP_INPUTEVENT_CLASS_MOUSE);
OutputDebugString(_T("Instance_DidCreate\r\n"));
return PP_TRUE;
}
void Instance_DidDestroy(PP_Instance instance) {
/* Find the matching item in the linked list, delete it, and patch the
* links.
*/
struct InstanceInfo** prev_ptr = &all_instances;
struct InstanceInfo* cur = all_instances;
while (cur) {
if (instance == cur->pp_instance) {
*prev_ptr = cur->next;
g_core_interface->ReleaseResource(cur->graphics);
free(cur);
return;
}
prev_ptr = &cur->next;
cur = cur->next;
}
}
void Instance_DidChangeView(PP_Instance pp_instance,
PP_Resource view) {
struct PP_Rect position;
struct InstanceInfo* info = FindInstance(pp_instance);
if (!info)
return;
if (g_view_interface->GetRect(view, &position) == PP_FALSE)
return;
if (info->last_size.width != position.size.width ||
info->last_size.height != position.size.height) {
info->last_size.width = position.size.width;
info->last_size.height = position.size.height;
/* Got a resize, repaint the plugin. */
Repaint(info, &position.size);
}
OutputDebugString(_T("Instance_DidChangeView\r\n"));
}
void Instance_DidChangeFocus(PP_Instance pp_instance, PP_Bool has_focus) {
}
PP_Bool Instance_HandleDocumentLoad(PP_Instance pp_instance,
PP_Resource pp_url_loader) {
return PP_FALSE;
}
static PPP_Instance instance_interface = {
&Instance_DidCreate,
&Instance_DidDestroy,
&Instance_DidChangeView,
&Instance_DidChangeFocus,
&Instance_HandleDocumentLoad
};
PP_Bool InputEvent_HandleInputEvent(PP_Instance instance, PP_Resource input_event)
{
struct PP_Point pt;
TCHAR szLog[512] = { 0 };
switch (g_input_interface->GetType(input_event))
{
case PP_INPUTEVENT_TYPE_MOUSEDOWN:
pt = g_mouse_interface->GetPosition(input_event);
_stprintf_s(szLog, 512, _T("InputEvent_HandleInputEvent, mouse down at [%d, %d]\r\n"), pt.x, pt.y);
OutputDebugString(szLog);
break;
default:
return PP_FALSE;
}
struct InstanceInfo* info = FindInstance(instance);
if (info && info->last_size.width > 0)
{
Repaint(info, &info->last_size);
}
return PP_TRUE;
}
static PPP_InputEvent input_interface = {
&InputEvent_HandleInputEvent
};
/* Global entrypoints --------------------------------------------------------*/
PP_EXPORT int32_t PPP_InitializeModule(PP_Module module,
PPB_GetInterface get_browser_interface) {
g_get_browser_interface = get_browser_interface;
g_core_interface = (const PPB_Core*)
get_browser_interface(PPB_CORE_INTERFACE);
g_instance_interface = (const PPB_Instance*)
get_browser_interface(PPB_INSTANCE_INTERFACE);
g_graphics_3d_interface = (const PPB_Graphics3D*)
get_browser_interface(PPB_GRAPHICS_3D_INTERFACE);
g_view_interface = (const PPB_View*)
get_browser_interface(PPB_VIEW_INTERFACE);
g_input_interface = (const PPB_InputEvent*)get_browser_interface(PPB_INPUT_EVENT_INTERFACE);
g_mouse_interface = (const PPB_MouseInputEvent*)get_browser_interface(PPB_MOUSE_INPUT_EVENT_INTERFACE);
if (!g_core_interface || !g_instance_interface ||
!g_graphics_3d_interface || !g_view_interface ||
!g_input_interface || !g_mouse_interface)
return -1;
if (GL_TRUE != glInitializePPAPI(get_browser_interface))
return -1;
OutputDebugString(_T("PPP_InitializeModule\r\n"));
return PP_OK;
}
PP_EXPORT void PPP_ShutdownModule() {
}
PP_EXPORT const void* PPP_GetInterface(const char* interface_name) {
if (strcmp(interface_name, PPP_INSTANCE_INTERFACE) == 0)
{
OutputDebugString(_T("PPP_GetInterface, instance_interface\r\n"));
return &instance_interface;
}
else if (strcmp(interface_name, PPP_INPUT_EVENT_INTERFACE) == 0)
{
OutputDebugString(_T("PPP_GetInterface, input_interface\r\n"));
return &input_interface;
}
return NULL;
}
改动部分我用foruok[1]、foruok[2]之类的注释标注出来了,共有5处。可以搜索查看。
InitTriangle方法在调用MakeAndBindGraphics3D之后调用,创建了shader、program object。DrawTriangle方法是在Repaint中被调用的,它内部通过glDrawArrays来绘制三角形。
好啦,这个改造过的示例依然很简单,不过可以演示如何在PPAPI中使用OpenGL ES。
其他参考文章:
- CEF Windows开发环境搭建
- CEF加载PPAPI插件
- VS2013编译最简单的PPAPI插件
- 理解PPAPI的设计
- PPAPI插件与浏览器的交互过程
- Windows下从源码编译CEF
- 编译PPAPI的media_stream_video示例
- PPAPI插件的绘图与输入事件处理
- 在PPAPI插件中创建本地窗口
- PPAPI插件与浏览器的通信
- Windows下从源码编译Skia
- 在PPAPI插件中使用Skia绘图
- 加载DLL中的图片资源生成Skia中的SkBitmap对象
- PPAPI+Skia实现的涂鸦板
- PPAPI中使用Chromium的3D图形接口