#include #include #include #include "test-conform-common.h" static const ClutterColor stage_color = { 0x0, 0x0, 0x0, 0xff }; #ifdef CLUTTER_COGL_HAS_GL /* Size the texture so that it is just off a power of two to enourage it so use software tiling when NPOTs aren't available */ #define TEXTURE_SIZE 257 #else /* CLUTTER_COGL_HAS_GL */ /* We can't use the funny-sized texture on GL ES because it will break cogl_texture_polygon. However there is only one code path for rendering quads so there is no need */ #define TEXTURE_SIZE 32 #endif /* CLUTTER_COGL_HAS_GL */ /* Amount of pixels to skip off the top, bottom, left and right of the texture when reading back the stage */ #define TEST_INSET 4 /* Size to actually render the texture at */ #define TEXTURE_RENDER_SIZE 32 typedef struct _TestState { guint frame; CoglHandle texture; } TestState; static gboolean validate_part (int xnum, int ynum, gboolean shown) { guchar *pixels, *p; ClutterActor *stage = clutter_stage_get_default (); gboolean ret = TRUE; /* Read the appropriate part but skip out a few pixels around the edges */ pixels = clutter_stage_read_pixels (CLUTTER_STAGE (stage), xnum * TEXTURE_RENDER_SIZE + TEST_INSET, ynum * TEXTURE_RENDER_SIZE + TEST_INSET, TEXTURE_RENDER_SIZE - TEST_INSET * 2, TEXTURE_RENDER_SIZE - TEST_INSET * 2); /* Make sure every pixels is the appropriate color */ for (p = pixels; p < pixels + ((TEXTURE_RENDER_SIZE - TEST_INSET * 2) * (TEXTURE_RENDER_SIZE - TEST_INSET * 2)); p += 4) { if (p[0] != (shown ? 255 : 0)) ret = FALSE; if (p[1] != 0) ret = FALSE; if (p[2] != 0) ret = FALSE; } g_free (pixels); return ret; } static void validate_result (TestState *state) { /* Front-facing texture */ g_assert (validate_part (0, 0, TRUE)); /* Front-facing texture with flipped tex coords */ g_assert (validate_part (1, 0, TRUE)); /* Back-facing texture */ g_assert (validate_part (2, 0, FALSE)); /* Front-facing texture polygon */ g_assert (validate_part (3, 0, TRUE)); /* Back-facing texture polygon */ g_assert (validate_part (4, 0, FALSE)); /* Regular rectangle */ g_assert (validate_part (5, 0, TRUE)); /* Backface culling disabled - everything should be shown */ /* Front-facing texture */ g_assert (validate_part (0, 1, TRUE)); /* Front-facing texture with flipped tex coords */ g_assert (validate_part (1, 1, TRUE)); /* Back-facing texture */ g_assert (validate_part (2, 1, TRUE)); /* Front-facing texture polygon */ g_assert (validate_part (3, 1, TRUE)); /* Back-facing texture polygon */ g_assert (validate_part (4, 1, TRUE)); /* Regular rectangle */ g_assert (validate_part (5, 1, TRUE)); /* Comment this out if you want visual feedback of what this test * paints. */ clutter_main_quit (); } static void on_paint (ClutterActor *actor, TestState *state) { int i; int frame_num; CoglHandle material = cogl_material_new (); cogl_material_set_layer_filters (material, 0, COGL_MATERIAL_FILTER_NEAREST, COGL_MATERIAL_FILTER_NEAREST); cogl_set_backface_culling_enabled (TRUE); cogl_push_matrix (); /* Render the scene twice - once with backface culling enabled and once without. The second time is translated so that it is below the first */ for (i = 0; i < 2; i++) { float x1 = 0, x2, y1 = 0, y2 = (float)(TEXTURE_RENDER_SIZE); CoglTextureVertex verts[4]; cogl_set_source (material); memset (verts, 0, sizeof (verts)); x2 = x1 + (float)(TEXTURE_RENDER_SIZE); /* Draw a front-facing texture */ cogl_material_set_layer (material, 0, state->texture); cogl_rectangle (x1, y1, x2, y2); x1 = x2; x2 = x1 + (float)(TEXTURE_RENDER_SIZE); /* Draw a front-facing texture with flipped texcoords */ cogl_material_set_layer (material, 0, state->texture); cogl_rectangle_with_texture_coords (x1, y1, x2, y2, 1.0, 0.0, 0.0, 1.0); x1 = x2; x2 = x1 + (float)(TEXTURE_RENDER_SIZE); /* Draw a back-facing texture */ cogl_material_set_layer (material, 0, state->texture); cogl_rectangle (x2, y1, x1, y2); x1 = x2; x2 = x1 + (float)(TEXTURE_RENDER_SIZE); /* Draw a front-facing texture polygon */ verts[0].x = x1; verts[0].y = y2; verts[1].x = x2; verts[1].y = y2; verts[2].x = x2; verts[2].y = y1; verts[3].x = x1; verts[3].y = y1; verts[0].tx = 0; verts[0].ty = 0; verts[1].tx = 1.0; verts[1].ty = 0; verts[2].tx = 1.0; verts[2].ty = 1.0; verts[3].tx = 0; verts[3].ty = 1.0; cogl_material_set_layer (material, 0, state->texture); cogl_polygon (verts, 4, FALSE); x1 = x2; x2 = x1 + (float)(TEXTURE_RENDER_SIZE); /* Draw a back-facing texture polygon */ verts[0].x = x1; verts[0].y = y1; verts[1].x = x2; verts[1].y = y1; verts[2].x = x2; verts[2].y = y2; verts[3].x = x1; verts[3].y = y2; verts[0].tx = 0; verts[0].ty = 0; verts[1].tx = 1.0; verts[1].ty = 0; verts[2].tx = 1.0; verts[2].ty = 1.0; verts[3].tx = 0; verts[3].ty = 1.0; cogl_material_set_layer (material, 0, state->texture); cogl_polygon (verts, 4, FALSE); x1 = x2; x2 = x1 + (float)(TEXTURE_RENDER_SIZE); /* Draw a regular rectangle (this should always show) */ cogl_set_source_color4f (1.0, 0, 0, 1.0); cogl_rectangle (x1, y1, x2, y2); /* The second time round draw beneath the first with backface culling disabled */ cogl_translate (0, TEXTURE_RENDER_SIZE, 0); cogl_set_backface_culling_enabled (FALSE); } cogl_pop_matrix (); cogl_handle_unref (material); /* XXX: Experiments have shown that for some buggy drivers, when using * glReadPixels there is some kind of race, so we delay our test for a * few frames and a few seconds: */ /* Need to increment frame first because clutter_stage_read_pixels fires a redraw */ frame_num = state->frame++; if (frame_num == 2) validate_result (state); else if (frame_num < 2) g_usleep (G_USEC_PER_SEC); } static gboolean queue_redraw (gpointer stage) { clutter_actor_queue_redraw (CLUTTER_ACTOR (stage)); return TRUE; } static CoglHandle make_texture (void) { guchar *tex_data, *p; CoglHandle tex; tex_data = g_malloc (TEXTURE_SIZE * TEXTURE_SIZE * 4); for (p = tex_data + TEXTURE_SIZE * TEXTURE_SIZE * 4; p > tex_data;) { *(--p) = 255; *(--p) = 0; *(--p) = 0; *(--p) = 255; } tex = cogl_texture_new_from_data (TEXTURE_SIZE, TEXTURE_SIZE, COGL_TEXTURE_NONE, COGL_PIXEL_FORMAT_RGBA_8888, COGL_PIXEL_FORMAT_ANY, TEXTURE_SIZE * 4, tex_data); g_free (tex_data); return tex; } void test_backface_culling (TestConformSimpleFixture *fixture, gconstpointer data) { TestState state; ClutterActor *stage; ClutterActor *group; guint idle_source; state.frame = 0; state.texture = make_texture (); stage = clutter_stage_get_default (); clutter_stage_set_color (CLUTTER_STAGE (stage), &stage_color); group = clutter_group_new (); clutter_container_add_actor (CLUTTER_CONTAINER (stage), group); /* We force continuous redrawing of the stage, since we need to skip * the first few frames, and we wont be doing anything else that * will trigger redrawing. */ idle_source = g_idle_add (queue_redraw, stage); g_signal_connect (group, "paint", G_CALLBACK (on_paint), &state); clutter_actor_show_all (stage); clutter_main (); g_source_remove (idle_source); cogl_handle_unref (state.texture); if (g_test_verbose ()) g_print ("OK\n"); }