Hi Adam,
Excerpts from Adam B's message of Sat Jul 17 18:58:12 +0100 2010:
> Hello all,
>
> This page touts the benefits of COGL over raw OpenGL:
> http://wiki.clutter-project.org/wiki/Cogl. I'm really excited about the
> pango text rendering and vertex/pixel buffer abstractions. I'd like to give
> COGL a try for a 3D game project but I've not been able to find good
> examples or documentation for COGL.
The Cogl reference manual can be found here:
http://docs.clutter-project.org/docs/cogl/1.0/ but sadly there aren't
currently any comprehensive introductions to Cogl.
I have to confess I was a bit cheeking claiming a few features on that
page that don't exist yet (so as to motivate me to implement them) but
luckily the features you want do already exist :-)
I'd love to encourage people to pick up Cogl for games so I'm eager to
help if I can. In general Cogl still isn't a very mature API so we are
still working hard on exposing more features, but hopefully we cover
enough already for your game.
>
> Would some kind sole please show me a simple COGL program that:
> 1) sets up a fullscreen COGL window (perspective projection)
> 2) draw a spinning 3D cube or triangle
> 3) draw the text "Hello COGL" in front of the spinning cube
> 4) draw text showing the current mouse coordinates
>
> I'd guess that the above example could probably be with the high-level
> clutter framework (actors etc), but the bigger goal is a 3D game where I
> need something closer to raw opengl api. I also realize that COGL cannot
> (currently) work without clutter. I'm just looking for the minimal amount
> of clutter code to use COGL.
I've written an example program that should demonstrate all of the
above. It's quite verbose because it doesn't use much of the convenience
that Clutter offers and because I tried to comment it quite heavily. The
source can be compiled like:
$ gcc -g3 -O0 -o crate crate.c `pkg-config --cflags --libs clutter-x11-1.0`
It assumes you have an image named "crate1.jpg" in the current directory
when run. You can fetch the image I used with:
$ wget http://www.20threed.com/images/tuts/crate1.jpg
I have to mention it doesn't start fullscreened by default because I was
actually seeing some window sizing problems with fullscreening when
testing myself and since I didn't have a chance to debug that yet I made
the 'f' key toggle fullscreen mode instead. It might work for you.
If you have any further questions about Cogl please don't hesitate to
ask!
kind regards,
- Robert
#include <clutter/clutter.h>
#include <cogl/cogl-pango.h>
/* The state for this example... */
typedef struct _Data
{
int framebuffer_width;
int framebuffer_height;
/* Note: In Clutter 1.4 CoglHandle will be deprecated and you would
* instead use CoglVertexBuffer *, CoglTexture * and CoglMaterial *
* here... */
CoglHandle vbo;
CoglHandle texture;
CoglHandle crate_material;
/* The example supports some simple mouse input, letting you pan the
* cube around the window */
gboolean grab;
float x_ref;
float y_ref;
float translate_x;
float translate_y;
float snapshot_translate_x;
float snapshot_translate_y;
int current_pointer_x;
int current_pointer_y;
/* The cube continually rotates around each axis. */
float rotate_x;
float rotate_y;
float rotate_z;
gboolean fullscreen;
CoglPangoFontMap *pango_font_map;
PangoContext *pango_context;
PangoFontDescription *pango_font_desc;
PangoLayout *hello_label;
int hello_label_width;
int hello_label_height;
PangoLayout *keys_label;
PangoLayout *pointer_pos_label;
} Data;
/* A custom vertex structure, where each vertex has an (x,y,z) position,
* and a (tx, ty) texture coordinate.
*/
typedef struct _SimpleVertex
{
float x, y, z;
float tx, ty;
} SimpleVertex;
/* A static identity matrix initialized for convenience. */
static CoglMatrix identity;
/* static colors initialized for convenience. */
static CoglColor black;
static CoglColor white;
/* A cube modelled as a list of triangles. Potentially this could be
* done more efficiently as a triangle strip or using a separate index
* array, but this way is pretty simple, if a little verbose. */
SimpleVertex vertices[] =
{
/* Front face */
{ /* pos = */ -1.0f, -1.0f, 1.0f, /* tex coords = */ 0.0f, 1.0f},
{ /* pos = */ 1.0f, -1.0f, 1.0f, /* tex coords = */ 1.0f, 1.0f},
{ /* pos = */ 1.0f, 1.0f, 1.0f, /* tex coords = */ 1.0f, 0.0f},
{ /* pos = */ -1.0f, -1.0f, 1.0f, /* tex coords = */ 0.0f, 1.0f},
{ /* pos = */ 1.0f, 1.0f, 1.0f, /* tex coords = */ 1.0f, 0.0f},
{ /* pos = */ -1.0f, 1.0f, 1.0f, /* tex coords = */ 0.0f, 0.0f},
/* Back face */
{ /* pos = */ -1.0f, -1.0f, -1.0f, /* tex coords = */ 1.0f, 0.0f},
{ /* pos = */ -1.0f, 1.0f, -1.0f, /* tex coords = */ 1.0f, 1.0f},
{ /* pos = */ 1.0f, 1.0f, -1.0f, /* tex coords = */ 0.0f, 1.0f},
{ /* pos = */ -1.0f, -1.0f, -1.0f, /* tex coords = */ 1.0f, 0.0f},
{ /* pos = */ 1.0f, 1.0f, -1.0f, /* tex coords = */ 0.0f, 1.0f},
{ /* pos = */ 1.0f, -1.0f, -1.0f, /* tex coords = */ 0.0f, 0.0f},
/* Top face */
{ /* pos = */ -1.0f, 1.0f, -1.0f, /* tex coords = */ 0.0f, 1.0f},
{ /* pos = */ -1.0f, 1.0f, 1.0f, /* tex coords = */ 0.0f, 0.0f},
{ /* pos = */ 1.0f, 1.0f, 1.0f, /* tex coords = */ 1.0f, 0.0f},
{ /* pos = */ -1.0f, 1.0f, -1.0f, /* tex coords = */ 0.0f, 1.0f},
{ /* pos = */ 1.0f, 1.0f, 1.0f, /* tex coords = */ 1.0f, 0.0f},
{ /* pos = */ 1.0f, 1.0f, -1.0f, /* tex coords = */ 1.0f, 1.0f},
/* Bottom face */
{ /* pos = */ -1.0f, -1.0f, -1.0f, /* tex coords = */ 1.0f, 1.0f},
{ /* pos = */ 1.0f, -1.0f, -1.0f, /* tex coords = */ 0.0f, 1.0f},
{ /* pos = */ 1.0f, -1.0f, 1.0f, /* tex coords = */ 0.0f, 0.0f},
{ /* pos = */ -1.0f, -1.0f, -1.0f, /* tex coords = */ 1.0f, 1.0f},
{ /* pos = */ 1.0f, -1.0f, 1.0f, /* tex coords = */ 0.0f, 0.0f},
{ /* pos = */ -1.0f, -1.0f, 1.0f, /* tex coords = */ 1.0f, 0.0f},
/* Right face */
{ /* pos = */ 1.0f, -1.0f, -1.0f, /* tex coords = */ 1.0f, 0.0f},
{ /* pos = */ 1.0f, 1.0f, -1.0f, /* tex coords = */ 1.0f, 1.0f},
{ /* pos = */ 1.0f, 1.0f, 1.0f, /* tex coords = */ 0.0f, 1.0f},
{ /* pos = */ 1.0f, -1.0f, -1.0f, /* tex coords = */ 1.0f, 0.0f},
{ /* pos = */ 1.0f, 1.0f, 1.0f, /* tex coords = */ 0.0f, 1.0f},
{ /* pos = */ 1.0f, -1.0f, 1.0f, /* tex coords = */ 0.0f, 0.0f},
/* Left face */
{ /* pos = */ -1.0f, -1.0f, -1.0f, /* tex coords = */ 0.0f, 0.0f},
{ /* pos = */ -1.0f, -1.0f, 1.0f, /* tex coords = */ 1.0f, 0.0f},
{ /* pos = */ -1.0f, 1.0f, 1.0f, /* tex coords = */ 1.0f, 1.0f},
{ /* pos = */ -1.0f, -1.0f, -1.0f, /* tex coords = */ 0.0f, 0.0f},
{ /* pos = */ -1.0f, 1.0f, 1.0f, /* tex coords = */ 1.0f, 1.0f},
{ /* pos = */ -1.0f, 1.0f, -1.0f, /* tex coords = */ 0.0f, 1.0f}
};
/* Since the pango renderer emits geometry in pixel/device coordinates
* and the anti aliasing is implemented with the assumption that the
* geometry *really* does end up pixel aligned, we setup a modelview
* matrix so that for geometry in the plane z = 0 we exactly map x
* coordinates in the range [0,stage_width] and y coordinates in the
* range [0,stage_height] to the framebuffer extents with (0,0) being
* the top left.
*
* This is what Clutter does for a ClutterStage, but this demonstrates
* how it is done manually using Cogl.
*/
void
setup_2d_device_coordinates_modelview (unsigned int width,
unsigned int height)
{
float z_camera;
CoglMatrix projection_matrix;
CoglMatrix mv_matrix;
/*
* In theory, we can compute the camera distance from screen as:
*
* 0.5 * tan (FOV)
*
* However, it's better to compute the z_camera from our projection
* matrix so that we get a 1:1 mapping at the screen distance. Consider
* the upper-left corner of the screen. It has object coordinates
* (0,0,0), so by the transform below, ends up with eye coordinate
*
* x_eye = x_object / width - 0.5 = - 0.5
* y_eye = (height - y_object) / width - 0.5 = 0.5
* z_eye = z_object / width - z_camera = - z_camera
*
* From cogl_perspective(), we know that the projection matrix has
* the form:
*
* (x, 0, 0, 0)
* (0, y, 0, 0)
* (0, 0, c, d)
* (0, 0, -1, 0)
*
* Applied to the above, we get clip coordinates of
*
* x_clip = x * (- 0.5)
* y_clip = y * 0.5
* w_clip = - 1 * (- z_camera) = z_camera
*
* Dividing through by w to get normalized device coordinates, we
* have, x_nd = x * 0.5 / z_camera, y_nd = - y * 0.5 / z_camera.
* The upper left corner of the screen has normalized device coordinates,
* (-1, 1), so to have the correct 1:1 mapping, we have to have:
*
* z_camera = 0.5 * x = 0.5 * y
*
* If x != y, then we have a non-uniform aspect ration, and a 1:1 mapping
* doesn't make sense.
*/
cogl_get_projection_matrix (&projection_matrix);
z_camera = 0.5 * projection_matrix.xx;
cogl_matrix_init_identity (&mv_matrix);
cogl_matrix_translate (&mv_matrix, -0.5f, -0.5f, -z_camera);
cogl_matrix_scale (&mv_matrix, 1.0f / width, -1.0f / height, 1.0f / width);
cogl_matrix_translate (&mv_matrix, 0.0f, -1.0 * height, 0.0f);
cogl_set_modelview_matrix (&mv_matrix);
}
/* Every time the stage is painted, this function is called and
* we can use Cogl to draw to the framebuffer.
*/
static void
cogl_paint_cb (ClutterActor *actor, Data *data)
{
char *text;
/* Currently ClutterStage doesn't give us a way to override the
* viewport and modelview / projection matrices and because you
* can't really predict when Clutter may re-assert them, if you want
* to control them manually then you currently need to assert them
* all every frame.
*/
cogl_set_viewport (0, 0, data->framebuffer_width, data->framebuffer_height);
cogl_perspective (60, /* field of view */
1, /* aspect ratio */
0.1, /* distance to near z plane */
100); /* distance to far z plane */
setup_2d_device_coordinates_modelview (data->framebuffer_width,
data->framebuffer_height);
cogl_clear (&black, COGL_BUFFER_BIT_COLOR);
cogl_push_matrix ();
cogl_translate (data->translate_x, data->translate_y, 0);
cogl_scale (75, 75, 75);
/* Rotate the cube separately around each axis.
*
* Note: Cogl matrix manipulation follows the same rules as for
* OpenGL. We use column-major matrices and - if you consider the
* transformations happening to the model - then they are combined
* in reverse order which is why the rotation is done last, since
* we want it to be a rotation around the origin, before it is
* scaled and translated.
*/
cogl_rotate (data->rotate_x++, 0, 0, 1);
cogl_rotate (data->rotate_y++, 0, 1, 0);
cogl_rotate (data->rotate_z++, 1, 0, 0);
/* Since the box is made of multiple triangles that will overlap
* when drawn and we don't control the order they are drawn in, we
* enable depth testing to make sure that triangles that shouldn't
* be visible get culled by the GPU.
*
* Note: we are planning to deprecate this API and instead depth
* state would be set on a CoglMaterial, something like:
* cogl_material_set_depth_test_enabled (material, TRUE);
* but for now this is how depth testing can be enabled... */
cogl_set_depth_test_enabled (TRUE);
/* Whenever you draw something with Cogl using geometry defined by
* one of cogl_rectangle, cogl_polygon, cogl_path or
* cogl_vertex_buffer then you have a current material that defines
* how that geometry should be processed.
*
* Here we are making our crate material current which will sample
* the crate texture when fragment processing. */
cogl_set_source (data->crate_material);
/* Give Cogl some geometry to draw. */
cogl_vertex_buffer_draw (data->vbo,
COGL_VERTICES_MODE_TRIANGLES,
0,
G_N_ELEMENTS (vertices));
cogl_set_depth_test_enabled (FALSE);
cogl_pop_matrix ();
/* And finally render our Pango layouts... */
cogl_pango_render_layout (data->hello_label,
(data->framebuffer_width / 2) -
(data->hello_label_width / 2),
(data->framebuffer_height / 2) -
(data->hello_label_height / 2),
&white, 0);
data->pointer_pos_label = pango_layout_new (data->pango_context);
pango_layout_set_font_description (data->pointer_pos_label,
data->pango_font_desc);
text = g_strdup_printf ("cursor position = (%4d,%4d)",
data->current_pointer_x,
data->current_pointer_y);
pango_layout_set_text (data->pointer_pos_label, text, -1);
g_free (text);
cogl_pango_render_layout (data->pointer_pos_label, 0, 0, &white, 0);
cogl_pango_render_layout (data->keys_label, 0, 30, &white, 0);
}
gboolean
button_press_cb (ClutterActor *actor, ClutterEvent *event, void *user_data)
{
Data *data = user_data;
data->grab = TRUE;
data->snapshot_translate_x = data->translate_x;
data->snapshot_translate_y = data->translate_y;
data->x_ref = event->button.x;
data->y_ref = event->button.y;
return TRUE;
}
gboolean
button_release_cb (ClutterActor *actor, ClutterEvent *event, void *user_data)
{
Data *data = user_data;
data->grab = FALSE;
return TRUE;
}
gboolean
motion_cb (ClutterActor *actor, ClutterEvent *event, void *user_data)
{
Data *data = user_data;
if (data->grab)
{
data->translate_x =
data->snapshot_translate_x + (event->motion.x - data->x_ref);
data->translate_y =
data->snapshot_translate_y + (event->motion.y - data->y_ref);
}
data->current_pointer_x = event->motion.x;
data->current_pointer_y = event->motion.y;
return TRUE;
}
gboolean
key_press_cb (ClutterActor *stage, ClutterEvent *event, void *user_data)
{
if (clutter_event_get_key_symbol (event) == CLUTTER_q ||
clutter_event_get_key_symbol (event) == CLUTTER_Q)
clutter_main_quit ();
else if (clutter_event_get_key_symbol (event) == CLUTTER_f ||
clutter_event_get_key_symbol (event) == CLUTTER_F)
{
Data *data = user_data;
data->fullscreen = !data->fullscreen;
clutter_stage_set_fullscreen (CLUTTER_STAGE (stage), data->fullscreen);
}
}
gboolean
queue_redraw (void *user_data)
{
clutter_actor_queue_redraw (user_data);
return TRUE;
}
static void
sync_framebuffer_size (ClutterActor *stage, Data *data)
{
data->framebuffer_width = clutter_actor_get_width (stage);
data->framebuffer_height = clutter_actor_get_height (stage);
}
static void
stage_allocation_changed_cb (ClutterActor *actor,
ClutterActorBox *box,
ClutterAllocationFlags flags,
void *user_data)
{
sync_framebuffer_size (actor, user_data);
}
int
main (int argc, char **argv)
{
Data data;
ClutterActor *stage;
float resolution;
PangoRectangle hello_label_size;
clutter_init (&argc, &argv);
/* Until we implement the planned CoglOnscreen framebuffer API
* you have to create a ClutterStage to create a window that can be
* drawn to using Cogl... */
stage = clutter_stage_new ();
sync_framebuffer_size (stage, &data);
/* From some backends, fullscreening/resizing a stage may happen
* asynchronously so we register a signal handler to re-sync with
* the stage size when ever the stage's allocation changes. */
g_signal_connect (stage, "allocation-changed",
G_CALLBACK (stage_allocation_changed_cb), &data);
/* Initialize some convenient constants */
cogl_matrix_init_identity (&identity);
cogl_color_set_from_4ub (&black, 0x00, 0x00, 0x00, 0xff);
cogl_color_set_from_4ub (&white, 0xff, 0xff, 0xff, 0xff);
/* Hook into the stage paint signal (after the stage has been cleared)
* so we can draw directly using Cogl */
g_signal_connect_after (stage, "paint", G_CALLBACK (cogl_paint_cb), &data);
/* Create a high level vertex buffer object (I explicitly note "high level"
* because this hides some of details of raw OpenGL vertex buffers which
* you may, or may not, appreciate.)
*
* For reference we are planning an improved vertex array API, and
* would appreciate any feedback about the new design being outlined
* in these pages:
*
* http://wiki.clutter-project.org/wiki/CoglDesign/CoglBuffer
* http://wiki.clutter-project.org/wiki/CoglDesign/CoglVertexArray
* http://wiki.clutter-project.org/wiki/CoglDesign/CoglVertexAttribute
* http://wiki.clutter-project.org/wiki/CoglDesign/CoglVertices
*/
data.vbo = cogl_vertex_buffer_new (G_N_ELEMENTS (vertices));
cogl_vertex_buffer_add (data.vbo,
"gl_Vertex",
3,
COGL_ATTRIBUTE_TYPE_FLOAT,
FALSE,
sizeof (SimpleVertex),
vertices);
cogl_vertex_buffer_add (data.vbo,
"gl_MultiTexCoord0",
2,
COGL_ATTRIBUTE_TYPE_FLOAT,
FALSE,
sizeof (SimpleVertex),
&vertices[0].tx);
/* Load a jpeg crate texture from a file */
data.texture = cogl_texture_new_from_file ("crate1.jpg",
COGL_TEXTURE_NO_SLICING,
COGL_PIXEL_FORMAT_ANY,
NULL);
/* a CoglMaterial conceptually describes all the state for vertex
* processing, fragment processing and blending geometry. When
* drawing the geometry for the crate this material says to sample a
* single texture during fragment processing... */
data.crate_material = cogl_material_new ();
cogl_material_set_layer (data.crate_material, 0,
data.texture);
/* Setup a Pango font map and context */
data.pango_font_map = COGL_PANGO_FONT_MAP (cogl_pango_font_map_new());
/* Note: in Clutter 1.4 this will be deprecated and the
* clutter_settings API should be used instead. */
resolution = clutter_backend_get_resolution (clutter_get_default_backend ());
cogl_pango_font_map_set_resolution (data.pango_font_map, resolution);
cogl_pango_font_map_set_use_mipmapping (data.pango_font_map, TRUE);
data.pango_context = cogl_pango_font_map_create_context (data.pango_font_map);
data.pango_font_desc = pango_font_description_new ();
pango_font_description_set_family (data.pango_font_desc, "Sans");
pango_font_description_set_size (data.pango_font_desc, 30 * PANGO_SCALE);
/* Setup the "Hello Cogl" text */
data.hello_label = pango_layout_new (data.pango_context);
pango_layout_set_font_description (data.hello_label, data.pango_font_desc);
pango_layout_set_text (data.hello_label, "Hello Cogl", -1);
pango_font_description_set_family (data.pango_font_desc, "Sans");
pango_font_description_set_size (data.pango_font_desc, 10 * PANGO_SCALE);
data.keys_label = pango_layout_new (data.pango_context);
pango_layout_set_font_description (data.keys_label, data.pango_font_desc);
pango_layout_set_text (data.keys_label,
"Press 'F' to toggle fullscreen\n"
"Press 'Q' to quit\n",
-1);
pango_layout_get_extents (data.hello_label, NULL, &hello_label_size);
data.hello_label_width = PANGO_PIXELS (hello_label_size.width);
data.hello_label_height = PANGO_PIXELS (hello_label_size.height);
/* Note: we leave the font description setup for the pointer position
* label which is created in cogl_paint_cb () */
/* Use Clutter for some input handling... */
data.grab = FALSE;
g_signal_connect (stage,
"button-press-event",
G_CALLBACK (button_press_cb),
&data);
g_signal_connect (stage,
"button-release-event",
G_CALLBACK (button_release_cb),
&data);
g_signal_connect (stage,
"motion-event",
G_CALLBACK (motion_cb),
&data);
g_signal_connect (stage,
"key-press-event",
G_CALLBACK (key_press_cb),
&data);
data.translate_x = data.framebuffer_width / 2;
data.translate_y = data.framebuffer_height / 2;
data.fullscreen = FALSE;
/* Normally Clutter only redraws the stage when some state changes and
* causes an actor to queue a redraw (e.g. in response to being
* moved)
*
* To allow drawing of animated content in Cogl then for simplicity this
* forces continuous redrawing of the stage.
*
* Note: this example doesn't drive its animation by wall clock time
* they are driven by the framerate which is not sensible for a real
* application.
*/
g_idle_add (queue_redraw, stage);
/* Display the stage and start the mainloop which will continually call
* our idle handler and signal any input events... */
clutter_actor_show (stage);
clutter_main ();
return 0;
}
_______________________________________________
clutter-app-devel-list mailing list
[email protected]
http://lists.clutter-project.org/listinfo/clutter-app-devel-list