/* * Cogl * * A Low Level GPU Graphics and Utilities API * * Copyright (C) 2007,2008,2009,2010 Intel Corporation. * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, copy, * modify, merge, publish, distribute, sublicense, and/or sell copies * of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * * */ #include "cogl-config.h" #include "cogl-debug.h" #include "cogl-context-private.h" #include "cogl-journal-private.h" #include "cogl-texture-private.h" #include "cogl-pipeline-private.h" #include "cogl-framebuffer-private.h" #include "cogl-attribute-private.h" #include "cogl-private.h" #include "cogl-meta-texture.h" #include "cogl-framebuffer-private.h" #include "cogl1-context.h" #include "cogl-primitives-private.h" #include #include #define _COGL_MAX_BEZ_RECURSE_DEPTH 16 typedef struct _TextureSlicedQuadState { CoglFramebuffer *framebuffer; CoglPipeline *pipeline; CoglTexture *main_texture; float tex_virtual_origin_x; float tex_virtual_origin_y; float quad_origin_x; float quad_origin_y; float v_to_q_scale_x; float v_to_q_scale_y; float quad_len_x; float quad_len_y; gboolean flipped_x; gboolean flipped_y; } TextureSlicedQuadState; typedef struct _TextureSlicedPolygonState { const CoglTextureVertex *vertices; int n_vertices; int stride; CoglAttribute **attributes; } TextureSlicedPolygonState; static void log_quad_sub_textures_cb (CoglTexture *texture, const float *subtexture_coords, const float *virtual_coords, void *user_data) { TextureSlicedQuadState *state = user_data; CoglFramebuffer *framebuffer = state->framebuffer; CoglTexture *texture_override; float quad_coords[4]; #define TEX_VIRTUAL_TO_QUAD(V, Q, AXIS) \ do { \ Q = V - state->tex_virtual_origin_##AXIS; \ Q *= state->v_to_q_scale_##AXIS; \ if (state->flipped_##AXIS) \ Q = state->quad_len_##AXIS - Q; \ Q += state->quad_origin_##AXIS; \ } while (0); TEX_VIRTUAL_TO_QUAD (virtual_coords[0], quad_coords[0], x); TEX_VIRTUAL_TO_QUAD (virtual_coords[1], quad_coords[1], y); TEX_VIRTUAL_TO_QUAD (virtual_coords[2], quad_coords[2], x); TEX_VIRTUAL_TO_QUAD (virtual_coords[3], quad_coords[3], y); #undef TEX_VIRTUAL_TO_QUAD COGL_NOTE (DRAW, "~~~~~ slice\n" "qx1: %f\t" "qy1: %f\n" "qx2: %f\t" "qy2: %f\n" "tx1: %f\t" "ty1: %f\n" "tx2: %f\t" "ty2: %f\n", quad_coords[0], quad_coords[1], quad_coords[2], quad_coords[3], subtexture_coords[0], subtexture_coords[1], subtexture_coords[2], subtexture_coords[3]); /* We only need to override the texture if it's different from the main texture */ if (texture == state->main_texture) texture_override = NULL; else texture_override = texture; _cogl_journal_log_quad (framebuffer->journal, quad_coords, state->pipeline, 1, /* one layer */ texture_override, /* replace the layer0 texture */ subtexture_coords, 4); } typedef struct _ValidateFirstLayerState { CoglPipeline *override_pipeline; } ValidateFirstLayerState; static gboolean validate_first_layer_cb (CoglPipeline *pipeline, int layer_index, void *user_data) { ValidateFirstLayerState *state = user_data; CoglPipelineWrapMode clamp_to_edge = COGL_PIPELINE_WRAP_MODE_CLAMP_TO_EDGE; CoglPipelineWrapMode wrap_s; CoglPipelineWrapMode wrap_t; /* We can't use hardware repeat so we need to set clamp to edge * otherwise it might pull in edge pixels from the other side. By * default WRAP_MODE_AUTOMATIC becomes CLAMP_TO_EDGE so we only need * to override if the wrap mode isn't already automatic or * clamp_to_edge. */ wrap_s = cogl_pipeline_get_layer_wrap_mode_s (pipeline, layer_index); if (wrap_s != COGL_PIPELINE_WRAP_MODE_CLAMP_TO_EDGE && wrap_s != COGL_PIPELINE_WRAP_MODE_AUTOMATIC) { if (!state->override_pipeline) state->override_pipeline = cogl_pipeline_copy (pipeline); cogl_pipeline_set_layer_wrap_mode_s (state->override_pipeline, layer_index, clamp_to_edge); } wrap_t = cogl_pipeline_get_layer_wrap_mode_t (pipeline, layer_index); if (wrap_t != COGL_PIPELINE_WRAP_MODE_CLAMP_TO_EDGE && wrap_t != COGL_PIPELINE_WRAP_MODE_AUTOMATIC) { if (!state->override_pipeline) state->override_pipeline = cogl_pipeline_copy (pipeline); cogl_pipeline_set_layer_wrap_mode_t (state->override_pipeline, layer_index, clamp_to_edge); } return FALSE; } /* This path doesn't currently support multitexturing but is used for * CoglTextures that don't support repeating using the GPU so we need to * manually emit extra geometry to fake the repeating. This includes: * * - CoglTexture2DSliced: when made of > 1 slice or if the users given * texture coordinates require repeating, * - CoglTexture2DAtlas: if the users given texture coordinates require * repeating, * - CoglTexturePixmap: if the users given texture coordinates require * repeating */ /* TODO: support multitexturing */ static void _cogl_texture_quad_multiple_primitives (CoglFramebuffer *framebuffer, CoglPipeline *pipeline, CoglTexture *texture, int layer_index, const float *position, float tx_1, float ty_1, float tx_2, float ty_2) { TextureSlicedQuadState state; gboolean tex_virtual_flipped_x; gboolean tex_virtual_flipped_y; gboolean quad_flipped_x; gboolean quad_flipped_y; ValidateFirstLayerState validate_first_layer_state; CoglPipelineWrapMode wrap_s, wrap_t; wrap_s = cogl_pipeline_get_layer_wrap_mode_s (pipeline, layer_index); wrap_t = cogl_pipeline_get_layer_wrap_mode_t (pipeline, layer_index); validate_first_layer_state.override_pipeline = NULL; cogl_pipeline_foreach_layer (pipeline, validate_first_layer_cb, &validate_first_layer_state); state.framebuffer = framebuffer; state.main_texture = texture; if (validate_first_layer_state.override_pipeline) state.pipeline = validate_first_layer_state.override_pipeline; else state.pipeline = pipeline; /* Get together the data we need to transform the virtual texture * coordinates of each slice into quad coordinates... * * NB: We need to consider that the quad coordinates and the texture * coordinates may be inverted along the x or y axis, and must preserve the * inversions when we emit the final geometry. */ #define X0 0 #define Y0 1 #define X1 2 #define Y1 3 tex_virtual_flipped_x = (tx_1 > tx_2) ? TRUE : FALSE; tex_virtual_flipped_y = (ty_1 > ty_2) ? TRUE : FALSE; state.tex_virtual_origin_x = tex_virtual_flipped_x ? tx_2 : tx_1; state.tex_virtual_origin_y = tex_virtual_flipped_y ? ty_2 : ty_1; quad_flipped_x = (position[X0] > position[X1]) ? TRUE : FALSE; quad_flipped_y = (position[Y0] > position[Y1]) ? TRUE : FALSE; state.quad_origin_x = quad_flipped_x ? position[X1] : position[X0]; state.quad_origin_y = quad_flipped_y ? position[Y1] : position[Y0]; /* flatten the two forms of coordinate inversion into one... */ state.flipped_x = tex_virtual_flipped_x ^ quad_flipped_x; state.flipped_y = tex_virtual_flipped_y ^ quad_flipped_y; /* We use the _len_AXIS naming here instead of _width and _height because * log_quad_slice_cb uses a macro with symbol concatenation to handle both * axis, so this is more convenient... */ state.quad_len_x = fabs (position[X1] - position[X0]); state.quad_len_y = fabs (position[Y1] - position[Y0]); #undef X0 #undef Y0 #undef X1 #undef Y1 state.v_to_q_scale_x = fabs (state.quad_len_x / (tx_2 - tx_1)); state.v_to_q_scale_y = fabs (state.quad_len_y / (ty_2 - ty_1)); /* For backwards compatibility the default wrap mode for cogl_rectangle() is * _REPEAT... */ if (wrap_s == COGL_PIPELINE_WRAP_MODE_AUTOMATIC) wrap_s = COGL_PIPELINE_WRAP_MODE_REPEAT; if (wrap_t == COGL_PIPELINE_WRAP_MODE_AUTOMATIC) wrap_t = COGL_PIPELINE_WRAP_MODE_REPEAT; cogl_meta_texture_foreach_in_region (COGL_META_TEXTURE (texture), tx_1, ty_1, tx_2, ty_2, wrap_s, wrap_t, log_quad_sub_textures_cb, &state); if (validate_first_layer_state.override_pipeline) cogl_object_unref (validate_first_layer_state.override_pipeline); } typedef struct _ValidateTexCoordsState { int i; int n_layers; const float *user_tex_coords; int user_tex_coords_len; float *final_tex_coords; CoglPipeline *override_pipeline; gboolean needs_multiple_primitives; } ValidateTexCoordsState; /* * Validate the texture coordinates for this rectangle. */ static gboolean validate_tex_coords_cb (CoglPipeline *pipeline, int layer_index, void *user_data) { ValidateTexCoordsState *state = user_data; CoglTexture *texture; const float *in_tex_coords; float *out_tex_coords; float default_tex_coords[4] = {0.0, 0.0, 1.0, 1.0}; CoglTransformResult transform_result; state->i++; /* FIXME: we should be able to avoid this copying when no * transform is required by the texture backend and the user * has supplied enough coordinates for all the layers. */ /* If the user didn't supply texture coordinates for this layer then use the default coords */ if (state->i >= state->user_tex_coords_len / 4) in_tex_coords = default_tex_coords; else in_tex_coords = &state->user_tex_coords[state->i * 4]; out_tex_coords = &state->final_tex_coords[state->i * 4]; memcpy (out_tex_coords, in_tex_coords, sizeof (float) * 4); texture = cogl_pipeline_get_layer_texture (pipeline, layer_index); /* NB: NULL textures are handled by _cogl_pipeline_flush_gl_state */ if (!texture) return TRUE; /* Convert the texture coordinates to GL. */ transform_result = _cogl_texture_transform_quad_coords_to_gl (texture, out_tex_coords); /* If the texture has waste or we are using GL_TEXTURE_RECT we * can't handle texture repeating so we can't use the layer if * repeating is required. * * NB: We already know that no texture matrix is being used if the * texture doesn't support hardware repeat. */ if (transform_result == COGL_TRANSFORM_SOFTWARE_REPEAT) { if (state->i == 0) { if (state->n_layers > 1) { static gboolean warning_seen = FALSE; if (!warning_seen) g_warning ("Skipping layers 1..n of your material since " "the first layer doesn't support hardware " "repeat (e.g. because of waste or use of " "GL_TEXTURE_RECTANGLE_ARB) and you supplied " "texture coordinates outside the range [0,1]." "Falling back to software repeat assuming " "layer 0 is the most important one keep"); warning_seen = TRUE; } if (state->override_pipeline) cogl_object_unref (state->override_pipeline); state->needs_multiple_primitives = TRUE; return FALSE; } else { static gboolean warning_seen = FALSE; if (!warning_seen) g_warning ("Skipping layer %d of your material " "since you have supplied texture coords " "outside the range [0,1] but the texture " "doesn't support hardware repeat (e.g. " "because of waste or use of " "GL_TEXTURE_RECTANGLE_ARB). This isn't " "supported with multi-texturing.", state->i); warning_seen = TRUE; cogl_pipeline_set_layer_texture (pipeline, layer_index, NULL); } } /* By default WRAP_MODE_AUTOMATIC becomes to CLAMP_TO_EDGE. If the texture coordinates need repeating then we'll override this to GL_REPEAT. Otherwise we'll leave it at CLAMP_TO_EDGE so that it won't blend in pixels from the opposite side when the full texture is drawn with GL_LINEAR filter mode */ if (transform_result == COGL_TRANSFORM_HARDWARE_REPEAT) { if (cogl_pipeline_get_layer_wrap_mode_s (pipeline, layer_index) == COGL_PIPELINE_WRAP_MODE_AUTOMATIC) { if (!state->override_pipeline) state->override_pipeline = cogl_pipeline_copy (pipeline); cogl_pipeline_set_layer_wrap_mode_s (state->override_pipeline, layer_index, COGL_PIPELINE_WRAP_MODE_REPEAT); } if (cogl_pipeline_get_layer_wrap_mode_t (pipeline, layer_index) == COGL_PIPELINE_WRAP_MODE_AUTOMATIC) { if (!state->override_pipeline) state->override_pipeline = cogl_pipeline_copy (pipeline); cogl_pipeline_set_layer_wrap_mode_t (state->override_pipeline, layer_index, COGL_PIPELINE_WRAP_MODE_REPEAT); } } return TRUE; } /* This path supports multitexturing but only when each of the layers is * handled with a single GL texture. Also if repeating is necessary then * _cogl_texture_can_hardware_repeat() must return TRUE. * This includes layers made from: * * - CoglTexture2DSliced: if only comprised of a single slice with optional * waste, assuming the users given texture coordinates don't require * repeating. * - CoglTexture{1D,2D}: always. * - CoglTexture2DAtlas: assuming the users given texture coordinates don't * require repeating. * - CoglTexturePixmap: assuming the users given texture coordinates don't * require repeating. */ static gboolean _cogl_multitexture_quad_single_primitive (CoglFramebuffer *framebuffer, CoglPipeline *pipeline, const float *position, const float *user_tex_coords, int user_tex_coords_len) { int n_layers = cogl_pipeline_get_n_layers (pipeline); ValidateTexCoordsState state; float *final_tex_coords = alloca (sizeof (float) * 4 * n_layers); state.i = -1; state.n_layers = n_layers; state.user_tex_coords = user_tex_coords; state.user_tex_coords_len = user_tex_coords_len; state.final_tex_coords = final_tex_coords; state.override_pipeline = NULL; state.needs_multiple_primitives = FALSE; cogl_pipeline_foreach_layer (pipeline, validate_tex_coords_cb, &state); if (state.needs_multiple_primitives) return FALSE; if (state.override_pipeline) pipeline = state.override_pipeline; _cogl_journal_log_quad (framebuffer->journal, position, pipeline, n_layers, NULL, /* no texture override */ final_tex_coords, n_layers * 4); if (state.override_pipeline) cogl_object_unref (state.override_pipeline); return TRUE; } typedef struct _ValidateLayerState { CoglContext *ctx; int i; int first_layer; CoglPipeline *override_source; gboolean all_use_sliced_quad_fallback; } ValidateLayerState; static gboolean _cogl_rectangles_validate_layer_cb (CoglPipeline *pipeline, int layer_index, void *user_data) { ValidateLayerState *state = user_data; CoglTexture *texture; state->i++; /* We need to ensure the mipmaps are ready before deciding * anything else about the texture because the texture storage * could completely change if it needs to be migrated out of the * atlas and will affect how we validate the layer. * * FIXME: this needs to be generalized. There could be any * number of things that might require a shuffling of the * underlying texture storage. We could add two mechanisms to * generalize this a bit... * * 1) add a _cogl_pipeline_layer_update_storage() function that * would for instance consider if mipmapping is necessary and * potentially migrate the texture from an atlas. * * 2) allow setting of transient primitive-flags on a pipeline * that may affect the outcome of _update_storage(). One flag * could indicate that we expect to sample beyond the bounds of * the texture border. * * flags = COGL_PIPELINE_PRIMITIVE_FLAG_VALID_BORDERS; * _cogl_pipeline_layer_assert_primitive_flags (layer, flags) * _cogl_pipeline_layer_update_storage (layer) * enqueue primitive in journal * * when the primitive is dequeued and drawn we should: * _cogl_pipeline_flush_gl_state (pipeline) * draw primitive * _cogl_pipeline_unassert_primitive_flags (layer, flags); * * _cogl_pipeline_layer_update_storage should take into * consideration all the asserted primitive requirements. (E.g. * there could be multiple primitives in the journal - or in a * renderlist in the future - that need mipmaps or that need * valid contents beyond their borders (for cogl_polygon) * meaning they can't work with textures in an atas, so * _cogl_pipeline_layer_update_storage would pass on these * requirements to the texture atlas backend which would make * sure the referenced texture is migrated out of the atlas and * mipmaps are generated.) */ _cogl_pipeline_pre_paint_for_layer (pipeline, layer_index); texture = cogl_pipeline_get_layer_texture (pipeline, layer_index); /* NULL textures are handled by * _cogl_pipeline_flush_gl_state */ if (texture == NULL) return TRUE; if (state->i == 0) state->first_layer = layer_index; /* XXX: * For now, if the first layer is sliced then all other layers are * ignored since we currently don't support multi-texturing with * sliced textures. If the first layer is not sliced then any other * layers found to be sliced will be skipped. (with a warning) * * TODO: Add support for multi-texturing rectangles with sliced * textures if no texture matrices are in use. */ if (cogl_texture_is_sliced (texture)) { if (state->i == 0) { if (cogl_pipeline_get_n_layers (pipeline) > 1) { static gboolean warning_seen = FALSE; if (!state->override_source) state->override_source = cogl_pipeline_copy (pipeline); _cogl_pipeline_prune_to_n_layers (state->override_source, 1); if (!warning_seen) g_warning ("Skipping layers 1..n of your pipeline since " "the first layer is sliced. We don't currently " "support any multi-texturing with sliced " "textures but assume layer 0 is the most " "important to keep"); warning_seen = TRUE; } state->all_use_sliced_quad_fallback = TRUE; return FALSE; } else { static gboolean warning_seen = FALSE; CoglTexture2D *tex_2d; if (!warning_seen) g_warning ("Skipping layer %d of your pipeline consisting of " "a sliced texture (unsupported for multi texturing)", state->i); warning_seen = TRUE; /* Note: currently only 2D textures can be sliced. */ tex_2d = state->ctx->default_gl_texture_2d_tex; cogl_pipeline_set_layer_texture (pipeline, layer_index, COGL_TEXTURE (tex_2d)); return TRUE; } } #ifdef COGL_ENABLE_DEBUG /* If the texture can't be repeated with the GPU (e.g. because it has * waste or if using GL_TEXTURE_RECTANGLE_ARB) then if a texture matrix * is also in use we don't know if the result will end up trying * to texture from the waste area. * * Note: we check can_hardware_repeat() first since it's cheaper. * * Note: cases where the texture coordinates will require repeating * will be caught by later validation. */ if (!_cogl_texture_can_hardware_repeat (texture) && _cogl_pipeline_layer_has_user_matrix (pipeline, layer_index)) { static gboolean warning_seen = FALSE; if (!warning_seen) g_warning ("layer %d of your pipeline uses a custom " "texture matrix but because the texture doesn't " "support hardware repeating you may see artefacts " "due to sampling beyond the texture's bounds.", state->i); warning_seen = TRUE; } #endif return TRUE; } void _cogl_framebuffer_draw_multitextured_rectangles ( CoglFramebuffer *framebuffer, CoglPipeline *pipeline, CoglMultiTexturedRect *rects, int n_rects) { CoglContext *ctx = framebuffer->context; CoglPipeline *original_pipeline; ValidateLayerState state; int i; original_pipeline = pipeline; /* * Validate all the layers of the current source pipeline... */ state.ctx = ctx; state.i = -1; state.first_layer = 0; state.override_source = NULL; state.all_use_sliced_quad_fallback = FALSE; cogl_pipeline_foreach_layer (pipeline, _cogl_rectangles_validate_layer_cb, &state); if (state.override_source) pipeline = state.override_source; /* * Emit geometry for each of the rectangles... */ for (i = 0; i < n_rects; i++) { CoglTexture *texture; const float default_tex_coords[4] = {0.0, 0.0, 1.0, 1.0}; const float *tex_coords; if (!state.all_use_sliced_quad_fallback) { gboolean success = _cogl_multitexture_quad_single_primitive (framebuffer, pipeline, rects[i].position, rects[i].tex_coords, rects[i].tex_coords_len); /* NB: If _cogl_multitexture_quad_single_primitive fails then it * means the user tried to use texture repeat with a texture that * can't be repeated by the GPU (e.g. due to waste or use of * GL_TEXTURE_RECTANGLE_ARB) */ if (success) continue; } /* If multitexturing failed or we are drawing with a sliced texture * then we only support a single layer so we pluck out the texture * from the first pipeline layer... */ texture = cogl_pipeline_get_layer_texture (pipeline, state.first_layer); if (rects[i].tex_coords) tex_coords = rects[i].tex_coords; else tex_coords = default_tex_coords; COGL_NOTE (DRAW, "Drawing Tex Quad (Multi-Prim Mode)"); _cogl_texture_quad_multiple_primitives (framebuffer, pipeline, texture, state.first_layer, rects[i].position, tex_coords[0], tex_coords[1], tex_coords[2], tex_coords[3]); } if (pipeline != original_pipeline) cogl_object_unref (pipeline); } void cogl_2d_primitives_immediate (CoglFramebuffer *framebuffer, CoglPipeline *pipeline, CoglVerticesMode mode, const CoglVertexP2 *vertices, unsigned int n_vertices) { CoglContext *ctx = framebuffer->context; CoglAttributeBuffer *attribute_buffer; CoglAttribute *attributes[1]; size_t vertices_size = sizeof (CoglVertexP2) * n_vertices; attribute_buffer = cogl_attribute_buffer_new (ctx, vertices_size, vertices); attributes[0] = cogl_attribute_new (attribute_buffer, "cogl_position_in", sizeof (CoglVertexP2), /* stride */ 0, /* offset */ 2, /* n_components */ COGL_ATTRIBUTE_TYPE_FLOAT); _cogl_framebuffer_draw_attributes (framebuffer, pipeline, mode, 0, /* first_index */ n_vertices, attributes, 1, COGL_DRAW_SKIP_JOURNAL_FLUSH | COGL_DRAW_SKIP_PIPELINE_VALIDATION | COGL_DRAW_SKIP_FRAMEBUFFER_FLUSH); cogl_object_unref (attributes[0]); cogl_object_unref (attribute_buffer); } void _cogl_rectangle_immediate (CoglFramebuffer *framebuffer, CoglPipeline *pipeline, float x_1, float y_1, float x_2, float y_2) { CoglVertexP2 vertices[4] = { {x_1, y_1}, {x_1, y_2}, {x_2, y_1}, {x_2, y_2} }; cogl_2d_primitives_immediate (framebuffer, pipeline, COGL_VERTICES_MODE_TRIANGLE_STRIP, vertices, 4); }