Reputation: 11
My question is how to speed up drawing on OpenGL on windows.
The test code is below. I copied it from some cairo example on the web.
the fps drop to 30 to 40 per second, even slower than a web browser.
just draw line every frame, I tried write javascript on html5. The same function just draws a line, and it runs much faster.
why cairo draw line on opengl so slow? Did I do something wrong? and how can I speed it up?
I think c++ should be much faster than javascript
#include <stdlib.h>
#include <stdio.h>
#include <malloc.h>
#define _USE_MATH_DEFINES
#include <math.h>
#include <iostream>
#include <chrono>
#include <random>
#include <gl/glut.h>
#include <gl/glext.h>
#include <cairo.h>
using namespace std;
double win_width = 800;
double win_height = 600;
double hw = win_width / 2;
double hh = win_height / 2;
double line_width = 1;
//double line_width = 1 / win_width;
cairo_surface_t * surf = NULL;
cairo_t * cr = NULL;
unsigned char * surf_data = NULL;
GLuint texture_id;
// Interface //
void opengl_init(void)
{
printf("OpenGL version: %s\n", glGetString(GL_VERSION));
printf("OpenGL vendor: %s\n", glGetString(GL_VENDOR));
printf("OpenGL renderer: %s\n", glGetString(GL_RENDERER));
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glDisable(GL_DEPTH_TEST);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_TEXTURE_RECTANGLE_ARB);
}
void opengl_cleanup(void)
{
glDeleteTextures(1, &texture_id);
}
void opengl_draw(int width, int height, unsigned char * surf_data)
{
if (!surf_data)
{
printf("draw_func() - No valid pointer to surface-data passed\n");
return;
}
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glClear(GL_COLOR_BUFFER_BIT);
glPushMatrix();
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, texture_id);
glTexImage2D(GL_TEXTURE_RECTANGLE_ARB,
0,
GL_RGBA,
width,
height,
0,
GL_BGRA,
GL_UNSIGNED_BYTE,
surf_data);
glColor3f(0.25f, 0.5f, 1.0f);
glBegin(GL_QUADS);
glTexCoord2f(0.0f, 0.0f);
glVertex2f(0.0f, 0.0f);
glTexCoord2f((GLfloat)width, 0.0f);
glVertex2f(1.0f, 0.0f);
glTexCoord2f((GLfloat)width, (GLfloat)height);
glVertex2f(1.0f, 1.0f);
glTexCoord2f(0.0f, (GLfloat)height);
glVertex2f(0.0f, 1.0f);
glEnd();
glPopMatrix();
}
void opengl_resize(int width, int height)
{
glViewport(0, 0, width, height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0.0f, 1.0f, 0.0f, 1.0f, -1.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glDeleteTextures(1, &texture_id);
glGenTextures(1, &texture_id);
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, texture_id);
glTexImage2D(GL_TEXTURE_RECTANGLE_ARB,
0,
GL_RGBA,
width,
height,
0,
GL_BGRA,
GL_UNSIGNED_BYTE,
NULL);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
}
void drawShape()
{
//save current brush
cairo_save(cr);
// clear background
cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
//cairo_scale(cr, (double)win_height / 1.0f, (double)win_height / 1.0f);
cairo_set_source_rgba(cr, 1, 1, 1, 1);
cairo_paint(cr);
//set line color and style
cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);
cairo_set_line_width(cr, line_width);
static double angle = 0;
angle += 0.01f;
//draw rect
cairo_set_source_rgba(cr, 1, 0, 0, 1);
//cairo_rectangle(cr, 0.5f + sinf(angle) * 0.1f, 0.5f, 0.1f, 0.1f);
cairo_rectangle(cr, hw + sin(angle) * 100, hh, 100, 100);
cairo_fill(cr);
cairo_stroke(cr);
//draw circle
cairo_set_source_rgba(cr, 0, 0, 1, 1);
cairo_arc(cr, 300, hh, 100, 0, 2 * M_PI);
//cairo_fill(cr);
cairo_stroke(cr);
//draw line
static double r = 100;
static double posx = 500;
static double posy = 500;
static double x = 0;
static double y = 0;
x = r * cosf(angle);
y = r * sinf(angle);
cairo_set_source_rgba(cr, 0, 1, 0, 1);
cairo_move_to(cr, x + posx, y + posy);
cairo_line_to(cr, -x + posx, -y + posy);
cairo_stroke(cr);
int minx = 5;
int maxx = win_width - 5;
int miny = 5;
int maxy = win_height - 5;
int n = 50 * 2;
std::default_random_engine randomEngine;
randomEngine.seed(std::chrono::steady_clock::now().time_since_epoch().count());
std::uniform_real_distribution<float> rangeX(minx, maxx);
std::uniform_real_distribution<float> rangeY(miny, maxy);
cairo_set_source_rgba(cr, 0, 0, 0, 1);
for (int i = 0; i < n * 2; i += 4)
{
float x1 = rangeX(randomEngine);
float y1 = rangeY(randomEngine);
float x2 = rangeX(randomEngine);
float y2 = rangeY(randomEngine);
cairo_move_to(cr, x1, y1);
cairo_line_to(cr, x2, y2);
}
cairo_stroke(cr);
//restore previous brush
cairo_restore(cr);
}
void display(void)
{
static int fps = 0;
static int frame = 0;
static long long startTime = chrono::system_clock::now().time_since_epoch().count();
static long long lastTime = 2;
long long now = chrono::system_clock::now().time_since_epoch().count();
++frame;
//update per second
if (now - lastTime > 10000000)
{
lastTime = now;
fps = frame;
frame = 0;
cout << fps << endl;
}
drawShape();
opengl_draw(win_width, win_height, surf_data);
glutSwapBuffers();
}
cairo_t*
create_cairo_context(int width,
int height,
int channels,
cairo_surface_t** surf,
unsigned char** buffer)
{
cairo_t* cr;
// create cairo-surface/context to act as OpenGL-texture source
*buffer = (unsigned char*)calloc(channels * width * height, sizeof(unsigned char));
if (!*buffer)
{
printf("create_cairo_context() - Couldn't allocate buffer\n");
return NULL;
}
*surf = cairo_image_surface_create_for_data(*buffer,
CAIRO_FORMAT_ARGB32,
width,
height,
channels * width);
if (cairo_surface_status(*surf) != CAIRO_STATUS_SUCCESS)
{
free(*buffer);
printf("create_cairo_context() - Couldn't create surface\n");
return NULL;
}
cr = cairo_create(*surf);
if (cairo_status(cr) != CAIRO_STATUS_SUCCESS)
{
free(*buffer);
printf("create_cairo_context() - Couldn't create context\n");
return NULL;
}
return cr;
}
void cleanup(void)
{
opengl_cleanup();
free(surf_data);
cairo_destroy(cr);
exit(0);
}
void keyboard(unsigned char key, int x, int y)
{
switch (key)
{
//27 is ESC key
case 27:
case 'q':
cleanup();
break;
case 'd':
cairo_surface_write_to_png(surf, "frame.png");
break;
case '+':
if (line_width < 10)
line_width += 1;
break;
case '-':
if (line_width > 1)
line_width -= 1;
break;
}
}
void idle(void)
{
glutPostRedisplay();
}
int main(int argc, char ** argv)
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);
glutInitWindowSize(win_width, win_height);
if (glutCreateWindow("Opengl Test") == 0)
exit(-2);
// create cairo-surface/context to act as OpenGL-texture source
cr = create_cairo_context(win_width, win_height, 4, &surf, &surf_data);
// setup "GL-context"
opengl_init();
glutDisplayFunc(display);
glutKeyboardFunc(keyboard);
glutIdleFunc(idle);
opengl_resize(win_width, win_height);
glutMainLoop();
return 0;
}
and here is the html and js i use
index.html
<!DOCTYPE html>
<html>
<head>
<title></title>
<script type="text/javascript" src="main.js"></script>
<style type="text/css">
html, body {
margin: 0px;
}
canvas {
display: block;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
</body>
</html>
main.js
window.onload = function() {
var canvas = document.getElementById("canvas"),
context = canvas.getContext("2d"),
width = canvas.width = window.innerWidth,
height = canvas.height = window.innerHeight;
render();
function render() {
context.clearRect(0, 0, width, height);
for(var i = 0; i < 100; i += 1){
context.beginPath();
context.moveTo(Math.random() * width, Math.random() * height);
context.lineTo(Math.random() * width, Math.random() * height);
context.stroke();
}
requestAnimationFrame(render);
}
};
Upvotes: 1
Views: 4513
Reputation: 484
It is very painful to compile cairo with OpenGL backend on Windows. But here's how I did it. Works on cygwin and msys2 with x86_64-w64-mingw32 toolchain.
Cygwin provides a precompiled cairo package for Win64. It doesn't support OpenGL. However if you install that package it will automatically pull in all the dependencies of cairo, including pixman. Also install the GLEW package (OpenGL Extension Wrangler).
Download the cairo source code, and extract it to your workspace.
Open the configure file. Search for -lGL
and replace all instances with -lglew32 -lopengl32
. This will make cairo link to the Windows OpenGL library instead of the Linux one. The library name here is case-sensitive, and must match the opengl32.dll
in C:\Windows\system32
. If not then the build system will complain about not able to find opengl32.dll
.
Open src/cairo-gl.h
. Under the line
#if CAIRO_HAS_GL_SURFACE || CAIRO_HAS_GLESV2_SURFACE
Add
#include <GL/glew.h>
Under the line
#if CAIRO_HAS_WGL_FUNCTIONS
Add
#include <GL/wglew.h>
This file must be included before windows.h
, otherwise GLEW will complain that gl.h
is alreaded included.
There is a bug in cairo wgl device creation process. Basically cairo will create an invisible window, and attach the GL context you provide to that window. However if your context does not have the same pixel format as the dummy window, then the binding will fail. Here's my quick fix. Open src/cairo-wgl-context.c
, find static cairo_status_t _wgl_dummy_ctx (cairo_wgl_context_t *ctx)
.
Before the line
wglMakeCurrent(ctx->dummy_dc, ctx->rc);
Add
ctx->rc = wglCreateContextAttribsARB(ctx->dummy_dc,ctx->rc,NULL);
Now execute
./configure --host=x86_64-w64-mingw32 --enable-gl --enable-wgl
make
If configure
complains it cannot find certain programs, first make sure the binutils for Win64 toolchain is installed. Then use ln
to make a hard link to the program with x86_64-w64-mingw32 prefix.
The make process will inevitably fail in the test
folder, because it attempts to compile tests that require the Linux GL library. Ignore the failure, and go to /src/.libs
. You should see libcairo-2.dll
and other files needed for linking.
The most common issue is that only libcairo.a
is produced, but no libcairo-2.dll
. Make sure you modified the configure
file exactly as I said.
Copy the libcairo-2.dll
to wherever you want. If your program links dynamically to cairo, it should now work fine. However if want to link statically, there is another complication. Some parts of cairo use Windows critical section to implement mutex. The critical sections are initialized during DllMain. However if you link statically then you must manually initialize them. Just call the void _cairo_mutex_initialize (void)
function within cairo-mutex.c
.
Upvotes: 1
Reputation: 162194
Your bottleneck is actually not OpenGL but Cairo. You're using Cairo with its standard software rasterizer backend; so the CPU is doing all the heavy lifting and OpenGL is just used as a glorified surface blitter. Admittedly the method for loading the finished image into OpenGL is not optimal (glTexSubImage2D should be used instead of glTexImage2D), but this is hardly your bottleneck there.
So what should you do: Ideally you'd be using a OpenGL accelerated backend for Cairo as described in http://cairographics.org/OpenGL/
Another option is ditching Cairo and use a vector rendering library directly targeted at OpenGL; I'm thinking of NanoVG here (I have no affiliations to this project). The main advantage of NanoVG is, that its whole internal architecture has been designed with OpenGL as backend in mind.
If you want to profile the influence on the improperly chosen method for texture upload here's a fixed variant of the code (remove opengl_cleanup
, it does nothing good for this very example and also get rid of opengl_resize
it's very bad practice to do projection setup in the resize handler).
void opengl_draw(int width, int height, void const * surf_data)
{
static GLuint texture_id = 0;
static int tex_width = 0;
static int tex_height = 0;
if (!surf_data)
{
printf("draw_func() - No valid pointer to surface-data passed\n");
return;
}
if( !texture_id ) {
glGenTextures(1, &texture_id);
}
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, texture_id);
if( width != tex_width || height != tex_height ) {
glTexImage2D(GL_TEXTURE_RECTANGLE_ARB,
0,
GL_RGBA,
tex_width = width,
tex_height = height,
0,
GL_BGRA,
GL_UNSIGNED_BYTE,
surf_data);
} else {
glTexSubImage2D(GL_TEXTURE_RECTANGLE_ARB,
0, 0, 0,
tex_width, tex_height,
GL_BGRA,
GL_UNSIGNED_BYTE,
surf_data);
}
glViewport(0, 0, width, height);
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
glOrtho(0.0f, 1.0f, 0.0f, 1.0f, -1.0f, 1.0f);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glPushMatrix();
glColor3f(0.25f, 0.5f, 1.0f);
glBegin(GL_QUADS);
glTexCoord2f(0.0f, 0.0f);
glVertex2f(0.0f, 0.0f);
glTexCoord2f((GLfloat)width, 0.0f);
glVertex2f(1.0f, 0.0f);
glTexCoord2f((GLfloat)width, (GLfloat)height);
glVertex2f(1.0f, 1.0f);
glTexCoord2f(0.0f, (GLfloat)height);
glVertex2f(0.0f, 1.0f);
glEnd();
glMatrixMode(GL_PROJECTION);
glPopMatrix();
glMatrixMode(GL_MODELVIEW);
glPopMatrix();
}
just draw line every frame, I tried write javascript on html5. The same function just draws a line, and it runs much faster.
And what do you think does this tell you? HTML Canvas may me implemented in any way that satisfies the specification. The Browser may use Cairo, its own rendering engine, may or may not use the GPU. This is not a useful comparison, because you don't know what you're actually comparing there.
Upvotes: 3