From c434f1fc48390a6e2c963656b039ebc3df0991af Mon Sep 17 00:00:00 2001 From: Robert Bragg Date: Wed, 26 May 2010 11:33:32 +0100 Subject: [PATCH] material: Make CoglMaterial responsible for depth state This redirects the legacy depth testing APIs through CoglMaterial and adds a new experimental cogl_material_ API for handling the depth testing state. This adds the following new functions: cogl_material_set_depth_test_enabled cogl_material_get_depth_test_enabled cogl_material_set_depth_writing_enabled cogl_material_get_depth_writing_enabled cogl_material_set_depth_test_function cogl_material_get_depth_test_function cogl_material_set_depth_range cogl_material_get_depth_range As with other experimental Cogl API you need to define COGL_ENABLE_EXPERIMENTAL_API to access them and their stability isn't yet guaranteed. --- clutter/cogl/cogl/cogl-context.c | 8 + clutter/cogl/cogl/cogl-context.h | 8 + clutter/cogl/cogl/cogl-material-private.h | 19 +- clutter/cogl/cogl/cogl-material.c | 323 +++++++++++++++++++- clutter/cogl/cogl/cogl-material.h | 207 +++++++++++++ clutter/cogl/cogl/cogl-types.h | 25 +- clutter/cogl/cogl/cogl.c | 28 +- clutter/cogl/cogl/cogl.h | 6 + tests/conform/Makefile.am | 1 + tests/conform/test-cogl-depth-test.c | 354 ++++++++++++++++++++++ tests/conform/test-conform-main.c | 1 + 11 files changed, 952 insertions(+), 28 deletions(-) create mode 100644 tests/conform/test-cogl-depth-test.c diff --git a/clutter/cogl/cogl/cogl-context.c b/clutter/cogl/cogl/cogl-context.c index e227594f7..585e124f0 100644 --- a/clutter/cogl/cogl/cogl-context.c +++ b/clutter/cogl/cogl/cogl-context.c @@ -127,6 +127,14 @@ cogl_create_context (void) _context->gl_blend_enable_cache = FALSE; + _context->depth_test_enabled_cache = FALSE; + _context->depth_test_function_cache = COGL_DEPTH_TEST_FUNCTION_LESS; + _context->depth_writing_enabled_cache = TRUE; + _context->depth_range_near_cache = 0; + _context->depth_range_far_cache = 1; + + _context->legacy_depth_test_enabled = FALSE; + _context->framebuffer_stack = _cogl_create_framebuffer_stack (); window_buffer = _cogl_onscreen_new (); diff --git a/clutter/cogl/cogl/cogl-context.h b/clutter/cogl/cogl/cogl-context.h index 530184f0b..a56f465de 100644 --- a/clutter/cogl/cogl/cogl-context.h +++ b/clutter/cogl/cogl/cogl-context.h @@ -109,6 +109,14 @@ typedef struct gboolean gl_blend_enable_cache; + gboolean depth_test_enabled_cache; + CoglDepthTestFunction depth_test_function_cache; + gboolean depth_writing_enabled_cache; + float depth_range_near_cache; + float depth_range_far_cache; + + gboolean legacy_depth_test_enabled; + /* PBOs */ /* This can be used to check if a pbo is bound */ CoglBuffer *current_pbo; diff --git a/clutter/cogl/cogl/cogl-material-private.h b/clutter/cogl/cogl/cogl-material-private.h index 770b60058..f98bdb387 100644 --- a/clutter/cogl/cogl/cogl-material-private.h +++ b/clutter/cogl/cogl/cogl-material-private.h @@ -311,8 +311,9 @@ typedef enum _CoglMaterialState COGL_MATERIAL_STATE_ALPHA_FUNC = 1L<<4, COGL_MATERIAL_STATE_BLEND = 1L<<5, COGL_MATERIAL_STATE_USER_SHADER = 1L<<6, + COGL_MATERIAL_STATE_DEPTH = 1L<<7, - COGL_MATERIAL_STATE_REAL_BLEND_ENABLE = 1L<<7, + COGL_MATERIAL_STATE_REAL_BLEND_ENABLE = 1L<<8, COGL_MATERIAL_STATE_ALL_SPARSE = COGL_MATERIAL_STATE_COLOR | @@ -321,7 +322,8 @@ typedef enum _CoglMaterialState COGL_MATERIAL_STATE_LIGHTING | COGL_MATERIAL_STATE_ALPHA_FUNC | COGL_MATERIAL_STATE_BLEND | - COGL_MATERIAL_STATE_USER_SHADER, + COGL_MATERIAL_STATE_USER_SHADER | + COGL_MATERIAL_STATE_DEPTH, COGL_MATERIAL_STATE_AFFECTS_BLENDING = COGL_MATERIAL_STATE_COLOR | @@ -335,7 +337,8 @@ typedef enum _CoglMaterialState COGL_MATERIAL_STATE_LIGHTING | COGL_MATERIAL_STATE_ALPHA_FUNC | COGL_MATERIAL_STATE_BLEND | - COGL_MATERIAL_STATE_USER_SHADER + COGL_MATERIAL_STATE_USER_SHADER | + COGL_MATERIAL_STATE_DEPTH } CoglMaterialState; @@ -388,12 +391,22 @@ typedef struct GLint blend_dst_factor_rgb; } CoglMaterialBlendState; +typedef struct +{ + gboolean depth_test_enabled; + CoglDepthTestFunction depth_test_function; + gboolean depth_writing_enabled; + float depth_range_near; + float depth_range_far; +} CoglMaterialDepthState; + typedef struct { CoglMaterialLightingState lighting_state; CoglMaterialAlphaFuncState alpha_state; CoglMaterialBlendState blend_state; CoglHandle user_program; + CoglMaterialDepthState depth_state; } CoglMaterialBigState; typedef enum diff --git a/clutter/cogl/cogl/cogl-material.c b/clutter/cogl/cogl/cogl-material.c index bfd0b4340..f30c533cc 100644 --- a/clutter/cogl/cogl/cogl-material.c +++ b/clutter/cogl/cogl/cogl-material.c @@ -347,6 +347,7 @@ _cogl_material_init_default_material (void) CoglMaterialLightingState *lighting_state = &big_state->lighting_state; CoglMaterialAlphaFuncState *alpha_state = &big_state->alpha_state; CoglMaterialBlendState *blend_state = &big_state->blend_state; + CoglMaterialDepthState *depth_state = &big_state->depth_state; _COGL_GET_CONTEXT (ctx, NO_RETVAL); @@ -409,6 +410,13 @@ _cogl_material_init_default_material (void) big_state->user_program = COGL_INVALID_HANDLE; + /* The same as the GL defaults */ + depth_state->depth_test_enabled = FALSE; + depth_state->depth_test_function = COGL_DEPTH_TEST_FUNCTION_LESS; + depth_state->depth_writing_enabled = TRUE; + depth_state->depth_range_near = 0; + depth_state->depth_range_far = 1; + ctx->default_material = _cogl_material_handle_new (material); } @@ -989,6 +997,13 @@ _cogl_material_copy_differences (CoglMaterial *dest, big_state->user_program = COGL_INVALID_HANDLE; } + if (differences & COGL_MATERIAL_STATE_DEPTH) + { + memcpy (&big_state->depth_state, + &src->big_state->depth_state, + sizeof (CoglMaterialDepthState)); + } + /* XXX: we shouldn't bother doing this in most cases since * _copy_differences is typically used to initialize material state * by copying it from the current authority, so it's not actually @@ -2890,6 +2905,15 @@ _cogl_material_blend_state_equal (CoglMaterial *authority0, return TRUE; } +static gboolean +_cogl_material_depth_state_equal (CoglMaterial *authority0, + CoglMaterial *authority1) +{ + return memcmp (&authority0->big_state->depth_state, + &authority1->big_state->depth_state, + sizeof (CoglMaterialDepthState)) == 0; +} + static gboolean _cogl_material_layers_equal (CoglMaterial *authority0, CoglMaterial *authority1) @@ -3103,6 +3127,19 @@ _cogl_material_equal (CoglHandle handle0, return FALSE; } + if (materials_difference & COGL_MATERIAL_STATE_DEPTH) + { + CoglMaterial *authority0 = + _cogl_material_get_authority (material0, + COGL_MATERIAL_STATE_DEPTH); + CoglMaterial *authority1 = + _cogl_material_get_authority (material1, + COGL_MATERIAL_STATE_DEPTH); + + if (!_cogl_material_depth_state_equal (authority0, authority1)) + return FALSE; + } + if (materials_difference & COGL_MATERIAL_STATE_LAYERS) { CoglMaterial *authority0 = @@ -3888,6 +3925,200 @@ _cogl_material_set_user_program (CoglHandle handle, handle_automatic_blend_enable (material, state); } +void +cogl_material_set_depth_test_enabled (CoglHandle handle, + gboolean enable) +{ + CoglMaterial *material = COGL_MATERIAL (handle); + CoglMaterialState state = COGL_MATERIAL_STATE_DEPTH; + CoglMaterial *authority; + CoglMaterialDepthState *depth_state; + + g_return_if_fail (cogl_is_material (handle)); + + authority = _cogl_material_get_authority (material, state); + + depth_state = &authority->big_state->depth_state; + if (depth_state->depth_test_enabled == enable) + return; + + /* - Flush journal primitives referencing the current state. + * - Make sure the material has no dependants so it may be modified. + * - If the material isn't currently an authority for the state being + * changed, then initialize that state from the current authority. + */ + _cogl_material_pre_change_notify (material, state, NULL); + + material->big_state->depth_state.depth_test_enabled = enable; + + _cogl_material_update_authority (material, authority, state, + _cogl_material_depth_state_equal); +} + +gboolean +cogl_material_get_depth_test_enabled (CoglHandle handle) +{ + CoglMaterial *material = COGL_MATERIAL (handle); + CoglMaterial *authority; + + g_return_val_if_fail (cogl_is_material (handle), FALSE); + + authority = + _cogl_material_get_authority (material, COGL_MATERIAL_STATE_DEPTH); + + return authority->big_state->depth_state.depth_test_enabled; +} + +void +cogl_material_set_depth_writing_enabled (CoglHandle handle, + gboolean enable) +{ + CoglMaterial *material = COGL_MATERIAL (handle); + CoglMaterialState state = COGL_MATERIAL_STATE_DEPTH; + CoglMaterial *authority; + CoglMaterialDepthState *depth_state; + + g_return_if_fail (cogl_is_material (handle)); + + authority = _cogl_material_get_authority (material, state); + + depth_state = &authority->big_state->depth_state; + if (depth_state->depth_writing_enabled == enable) + return; + + /* - Flush journal primitives referencing the current state. + * - Make sure the material has no dependants so it may be modified. + * - If the material isn't currently an authority for the state being + * changed, then initialize that state from the current authority. + */ + _cogl_material_pre_change_notify (material, state, NULL); + + material->big_state->depth_state.depth_writing_enabled = enable; + + _cogl_material_update_authority (material, authority, state, + _cogl_material_depth_state_equal); +} + +gboolean +cogl_material_get_depth_writing_enabled (CoglHandle handle) +{ + CoglMaterial *material = COGL_MATERIAL (handle); + CoglMaterial *authority; + + g_return_val_if_fail (cogl_is_material (handle), TRUE); + + authority = + _cogl_material_get_authority (material, COGL_MATERIAL_STATE_DEPTH); + + return authority->big_state->depth_state.depth_writing_enabled; +} + +void +cogl_material_set_depth_test_function (CoglHandle handle, + CoglDepthTestFunction function) +{ + CoglMaterial *material = COGL_MATERIAL (handle); + CoglMaterialState state = COGL_MATERIAL_STATE_DEPTH; + CoglMaterial *authority; + CoglMaterialDepthState *depth_state; + + g_return_if_fail (cogl_is_material (handle)); + + authority = _cogl_material_get_authority (material, state); + + depth_state = &authority->big_state->depth_state; + if (depth_state->depth_test_function == function) + return; + + /* - Flush journal primitives referencing the current state. + * - Make sure the material has no dependants so it may be modified. + * - If the material isn't currently an authority for the state being + * changed, then initialize that state from the current authority. + */ + _cogl_material_pre_change_notify (material, state, NULL); + + material->big_state->depth_state.depth_test_function = function; + + _cogl_material_update_authority (material, authority, state, + _cogl_material_depth_state_equal); +} + +CoglDepthTestFunction +cogl_material_get_depth_test_function (CoglHandle handle) +{ + CoglMaterial *material = COGL_MATERIAL (handle); + CoglMaterial *authority; + + g_return_val_if_fail (cogl_is_material (handle), + COGL_DEPTH_TEST_FUNCTION_LESS); + + authority = + _cogl_material_get_authority (material, COGL_MATERIAL_STATE_DEPTH); + + return authority->big_state->depth_state.depth_test_function; +} + + +gboolean +cogl_material_set_depth_range (CoglHandle handle, + float near, + float far, + GError **error) +{ +#ifndef COGL_HAS_GLES + CoglMaterial *material = COGL_MATERIAL (handle); + CoglMaterialState state = COGL_MATERIAL_STATE_DEPTH; + CoglMaterial *authority; + CoglMaterialDepthState *depth_state; + + g_return_val_if_fail (cogl_is_material (handle), FALSE); + + authority = _cogl_material_get_authority (material, state); + + depth_state = &authority->big_state->depth_state; + if (depth_state->depth_range_near == near && + depth_state->depth_range_far == far) + return TRUE; + + /* - Flush journal primitives referencing the current state. + * - Make sure the material has no dependants so it may be modified. + * - If the material isn't currently an authority for the state being + * changed, then initialize that state from the current authority. + */ + _cogl_material_pre_change_notify (material, state, NULL); + + material->big_state->depth_state.depth_range_near = near; + material->big_state->depth_state.depth_range_far = far; + + _cogl_material_update_authority (material, authority, state, + _cogl_material_depth_state_equal); + return TRUE; +#else + g_set_error (error, + COGL_ERROR, + COGL_ERROR_MISSING_FEATURE, + "glDepthRange not available on GLES 1"); + return FALSE; +#endif +} + +void +cogl_material_get_depth_range (CoglHandle handle, + float *near, + float *far) +{ + CoglMaterial *material = COGL_MATERIAL (handle); + CoglMaterial *authority; + + g_return_if_fail (cogl_is_material (handle)); + + authority = + _cogl_material_get_authority (material, COGL_MATERIAL_STATE_DEPTH); + + *near = authority->big_state->depth_state.depth_range_near; + *far = authority->big_state->depth_state.depth_range_far; +} + static CoglMaterialLayer * _cogl_material_layer_copy (CoglMaterialLayer *src) { @@ -5838,9 +6069,45 @@ blend_factor_uses_constant (GLenum blend_factor) #endif static void -_cogl_material_flush_color_blend_alpha_state (CoglMaterial *material, - unsigned long materials_difference, - gboolean skip_gl_color) +flush_depth_state (CoglMaterialDepthState *depth_state) +{ + _COGL_GET_CONTEXT (ctx, NO_RETVAL); + + if (ctx->depth_test_function_cache != depth_state->depth_test_function) + { + GE (glDepthFunc (depth_state->depth_test_function)); + ctx->depth_test_function_cache = depth_state->depth_test_function; + } + + if (ctx->depth_writing_enabled_cache != depth_state->depth_writing_enabled) + { + GE (glDepthMask (depth_state->depth_writing_enabled ? + GL_TRUE : GL_FALSE)); + ctx->depth_writing_enabled_cache = depth_state->depth_writing_enabled; + } + +#ifndef COGL_HAS_GLES + if (ctx->depth_range_near_cache != depth_state->depth_range_near || + ctx->depth_range_far_cache != depth_state->depth_range_far) + { +#ifdef COGL_HAS_GLES2 + GE (glDepthRangef (depth_state->depth_range_near, + depth_state->depth_range_far)); +#else + GE (glDepthRange (depth_state->depth_range_near, + depth_state->depth_range_far)); +#endif + ctx->depth_range_near_cache = depth_state->depth_range_near; + ctx->depth_range_far_cache = depth_state->depth_range_far; + } +#endif /* COGL_HAS_GLES */ +} + +static void +_cogl_material_flush_color_blend_alpha_depth_state ( + CoglMaterial *material, + unsigned long materials_difference, + gboolean skip_gl_color) { _COGL_GET_CONTEXT (ctx, NO_RETVAL); @@ -5948,12 +6215,36 @@ _cogl_material_flush_color_blend_alpha_state (CoglMaterial *material, alpha_state->alpha_func_reference)); } + if (materials_difference & COGL_MATERIAL_STATE_DEPTH) + { + CoglMaterial *authority = + _cogl_material_get_authority (material, COGL_MATERIAL_STATE_DEPTH); + CoglMaterialDepthState *depth_state = &authority->big_state->depth_state; + + if (depth_state->depth_test_enabled) + { + if (ctx->depth_test_enabled_cache != TRUE) + { + GE (glEnable (GL_DEPTH_TEST)); + ctx->depth_test_enabled_cache = depth_state->depth_test_enabled; + } + flush_depth_state (depth_state); + } + else if (ctx->depth_test_enabled_cache != FALSE) + { + GE (glDisable (GL_DEPTH_TEST)); + ctx->depth_test_enabled_cache = depth_state->depth_test_enabled; + } + } + if (material->real_blend_enable != ctx->gl_blend_enable_cache) { if (material->real_blend_enable) GE (glEnable (GL_BLEND)); else GE (glDisable (GL_BLEND)); + /* XXX: we shouldn't update any other blend state if blending + * is disabled! */ ctx->gl_blend_enable_cache = material->real_blend_enable; } } @@ -6129,9 +6420,9 @@ _cogl_material_flush_common_gl_state (CoglMaterial *material, _COGL_GET_CONTEXT (ctx, NO_RETVAL); - _cogl_material_flush_color_blend_alpha_state (material, - materials_difference, - skip_gl_color); + _cogl_material_flush_color_blend_alpha_depth_state (material, + materials_difference, + skip_gl_color); state.i = 0; state.layer_differences = layer_differences; @@ -6568,16 +6859,18 @@ _cogl_material_apply_legacy_state (CoglHandle handle) { _COGL_GET_CONTEXT (ctx, NO_RETVAL); + /* It was a mistake that we ever copied the OpenGL style API for + * associating these things directly with the context when we + * originally wrote Cogl. Until the corresponding deprecated APIs + * can be removed though we now shoehorn the state changes through + * the cogl_material API instead. + */ + if (ctx->current_program) - { - /* It was a mistake that we ever copied the OpenGL style API for - * making a program current (cogl_program_use) on the context. - * Until cogl_program_use is removed we will transparently set - * the program on the material because the cogl-material code is - * in the best position to juggle the corresponding GL state. */ - _cogl_material_set_user_program (handle, - ctx->current_program); - } + _cogl_material_set_user_program (handle, ctx->current_program); + + if (ctx->legacy_depth_test_enabled) + cogl_material_set_depth_test_enabled (handle, TRUE); } void diff --git a/clutter/cogl/cogl/cogl-material.h b/clutter/cogl/cogl/cogl-material.h index 12c9b39a0..0071dc36a 100644 --- a/clutter/cogl/cogl/cogl-material.h +++ b/clutter/cogl/cogl/cogl-material.h @@ -944,6 +944,213 @@ cogl_material_layer_get_wrap_mode_s (CoglHandle layer); CoglMaterialWrapMode cogl_material_layer_get_wrap_mode_t (CoglHandle layer); + +/* XXX: should this be CoglMaterialDepthTestFunction? + * It makes it very verbose but would be consistent with + * CoglMaterialWrapMode */ + +/** + * CoglDepthTestFunction: + * @COGL_DEPTH_TEST_FUNCTION_NEVER: Never passes. + * @COGL_DEPTH_TEST_FUNCTION_LESS: Passes if the fragment's depth + * value is less than the value currently in the depth buffer. + * @COGL_DEPTH_TEST_FUNCTION_EQUAL: Passes if the fragment's depth + * value is equal to the value currently in the depth buffer. + * @COGL_DEPTH_TEST_FUNCTION_LEQUAL: Passes if the fragment's depth + * value is less or equal to the value currently in the depth buffer. + * @COGL_DEPTH_TEST_FUNCTION_GREATER: Passes if the fragment's depth + * value is greater than the value currently in the depth buffer. + * @COGL_DEPTH_TEST_FUNCTION_NOTEQUAL: Passes if the fragment's depth + * value is not equal to the value currently in the depth buffer. + * @COGL_DEPTH_TEST_FUNCTION_GEQUAL: Passes if the fragment's depth + * value greater than or equal to the value currently in the depth buffer. + * @COGL_DEPTH_TEST_FUNCTION_ALWAYS: Always passes. + * + * When using depth testing one of these functions is used to compare + * the depth of an incoming fragment against the depth value currently + * stored in the depth buffer. The function is changed using + * cogl_material_set_depth_test_function(). + * + * The test is only done when depth testing is explicitly enabled. (See + * cogl_material_set_depth_test_enabled()) + */ +typedef enum +{ + COGL_DEPTH_TEST_FUNCTION_NEVER = GL_NEVER, + COGL_DEPTH_TEST_FUNCTION_LESS = GL_LESS, + COGL_DEPTH_TEST_FUNCTION_EQUAL = GL_EQUAL, + COGL_DEPTH_TEST_FUNCTION_LEQUAL = GL_LEQUAL, + COGL_DEPTH_TEST_FUNCTION_GREATER = GL_GREATER, + COGL_DEPTH_TEST_FUNCTION_NOTEQUAL = GL_NOTEQUAL, + COGL_DEPTH_TEST_FUNCTION_GEQUAL = GL_GEQUAL, + COGL_DEPTH_TEST_FUNCTION_ALWAYS = GL_ALWAYS +} CoglDepthTestFunction; +/* XXX: to avoid having to split this into a separate include that can + * in #included internally without needing the + * COGL_ENABLE_EXPERIMENTAL_API define this isn't guarded. It's still + * considered experimental but it's guarded instead by the fact that + * there's no corresponding API. */ + +#ifdef COGL_ENABLE_EXPERIMENTAL_API + +/** + * cogl_material_set_depth_test_enabled: + * @handle: A CoglMaterial handle + * @enable: The enable state you want + * + * Enables or disables depth testing according to the value of + * @enable. + * + * If depth testing is enable then the #CoglDepthTestFunction set + * using cogl_material_set_depth_test_function() us used to evaluate + * the depth value of incoming fragments against the corresponding + * value stored in the current depth buffer, and if the test passes + * then the fragments depth value is used to update the depth buffer. + * (unless you have disabled depth writing via + * cogl_material_set_depth_writing_enabled ()) + * + * By default depth testing is disabled. + * + * Since: 1.4 + */ +void +cogl_material_set_depth_test_enabled (CoglHandle handle, + gboolean enable); + +/** + * cogl_material_get_depth_test_enabled: + * @handle: A CoglMaterial handle + * + * Gets the current depth test enabled state as previously set by + * cogl_material_set_depth_test_enabled(). + * + * Returns: The material's current depth test enabled state. + * Since: 1.4 + */ +gboolean +cogl_material_get_depth_test_enabled (CoglHandle handle); + +/** + * cogl_material_set_depth_writing_enabled: + * @handle: A CoglMaterial handle + * @enable: The enable state you want + * + * Enables or disables depth buffer writing according to the value of + * @enable. Normally when depth testing is enabled and the comparison + * between a fragment's depth value and the corresponding depth buffer + * value passes then the fragment's depth is written to the depth + * buffer unless writing is disabled here. + * + * By default depth writing is enabled + * + * Since: 1.4 + */ +void +cogl_material_set_depth_writing_enabled (CoglHandle handle, + gboolean enable); + +/** + * cogl_material_get_depth_writing_enabled: + * @handle: A CoglMaterial handle + * Since: 1.4 + * + * Gets the depth writing enable state as set by the corresponding + * cogl_material_set_depth_writing_enabled. + * + * Returns: The current depth writing enable state + * Since: 1.4 + */ +gboolean +cogl_material_get_depth_writing_enabled (CoglHandle handle); + +/** + * cogl_material_set_depth_test_function: + * @handle: A CoglMaterial handle + * @function: The #CoglDepthTestFunction to set + * + * Sets the #CoglDepthTestFunction used to compare the depth value of + * an incoming fragment against the corresponding value in the current + * depth buffer. + * + * Since: 1.4 + */ +void +cogl_material_set_depth_test_function (CoglHandle handle, + CoglDepthTestFunction function); + +/** + * cogl_material_get_depth_test_function: + * @handle: A CoglMaterial handle + * + * Gets the current depth test enable state as previously set via + * cogl_material_set_depth_test_enabled(). + * + * Returns: The current depth test enable state. + * Since: 1.4 + */ +CoglDepthTestFunction +cogl_material_get_depth_test_function (CoglHandle handle); + +/** + * cogl_material_set_depth_range: + * @handle: A CoglMaterial handle + * @near: The near component of the desired depth range which will be + * clamped to the range [0, 1] + * @far: The far component of the desired depth range which will be + * clamped to the range [0, 1] + * @error: location to store an error of type #CoglError + * + * Sets the range to map depth values in normalized device coordinates + * to before writing out to a depth buffer. + * + * After your geometry has be transformed, clipped and had perspective + * division applied placing it in normalized device + * coordinates all depth values between the near and far z clipping + * planes are in the range -1 to 1. Before writing any depth value to + * the depth buffer though the value is mapped into the range [0, 1]. + * + * With this function you can change the range which depth values are + * mapped too although the range must still lye within the range [0, + * 1]. + * + * If your driver does not support this feature (for example you are + * using GLES 1 drivers) then this will return %FALSE and set an error + * if @error isn't NULL. You can check ahead of time for the + * %COGL_FEATURE_DEPTH_RANGE feature with cogl_features_available() to + * know if this function will succeed. + * + * By default normalized device coordinate depth values are mapped to + * the full range of depth buffer values, [0, 1]. + * + * Returns: %TRUE if driver support is available else %FALSE. + * + * Since: 1.4 + */ +gboolean +cogl_material_set_depth_range (CoglHandle handle, + float near, + float far, + GError **error); + +/** + * cogl_material_get_depth_range_mapping: + * @handle: A CoglMaterial handle + * @near: A pointer to store the near component of the depth range + * @far: A pointer to store the far component of the depth range + * + * Gets the current range to which normalized depth values are mapped + * before writing to the depth buffer. This corresponds to the range + * set with cogl_material_set_depth_range(). + * + * Since: 1.4 + */ +void +cogl_material_get_depth_range (CoglHandle handle, + float *near, + float *far); + +#endif /* COGL_ENABLE_EXPERIMENTAL_API */ + G_END_DECLS #endif /* __COGL_MATERIAL_H__ */ diff --git a/clutter/cogl/cogl/cogl-types.h b/clutter/cogl/cogl/cogl-types.h index 68b6dca4f..54d6d5e2b 100644 --- a/clutter/cogl/cogl/cogl-types.h +++ b/clutter/cogl/cogl/cogl-types.h @@ -233,6 +233,7 @@ typedef enum { /*< prefix=COGL_PIXEL_FORMAT >*/ * @COGL_FEATURE_UNSIGNED_INT_INDICES: Set if * %COGL_INDICES_TYPE_UNSIGNED_INT is supported in * cogl_vertex_buffer_indices_new(). + * @COGL_FEATURE_DEPTH_RANGE: cogl_material_set_depth_range() support * * Flags for the supported features. * @@ -252,7 +253,8 @@ typedef enum COGL_FEATURE_STENCIL_BUFFER = (1 << 10), COGL_FEATURE_VBOS = (1 << 11), COGL_FEATURE_PBOS = (1 << 12), - COGL_FEATURE_UNSIGNED_INT_INDICES = (1 << 13) + COGL_FEATURE_UNSIGNED_INT_INDICES = (1 << 13), + COGL_FEATURE_DEPTH_RANGE = (1 << 14) } CoglFeatureFlags; /** @@ -403,6 +405,27 @@ typedef enum { /*< prefix=COGL_BLEND_STRING_ERROR >*/ GQuark cogl_blend_string_error_quark (void); +#define COGL_ERROR (_cogl_error_quark ()) + +/** + * CoglError: + * @COGL_ERROR_MISSING_FEATURE: You tried to use a feature not + * currently available; likely because of missing driver support. + * + * Error enumeration for Cogl + * + * Currently this is only used by Cogl API marked as experimental so + * this enum should also be considered experimental. + * + * Since: 1.4 + */ +typedef enum { /*< prefix=COGL_ERROR >*/ + COGL_ERROR_MISSING_FEATURE +} CoglError; + +GQuark +_cogl_error_quark (void); + G_END_DECLS #endif /* __COGL_TYPES_H__ */ diff --git a/clutter/cogl/cogl/cogl.c b/clutter/cogl/cogl/cogl.c index 81467b74b..46e17ac76 100644 --- a/clutter/cogl/cogl/cogl.c +++ b/clutter/cogl/cogl/cogl.c @@ -287,25 +287,28 @@ _cogl_get_enable (void) return ctx->enable_flags; } +/* XXX: This API has been deprecated */ void cogl_set_depth_test_enabled (gboolean setting) { - /* Currently the journal can't track changes to depth state... */ - _cogl_journal_flush (); + _COGL_GET_CONTEXT (ctx, NO_RETVAL); - if (setting) - { - glEnable (GL_DEPTH_TEST); - glDepthFunc (GL_LEQUAL); - } + if (ctx->legacy_depth_test_enabled == setting) + return; + + ctx->legacy_depth_test_enabled = setting; + if (ctx->legacy_depth_test_enabled) + ctx->legacy_state_set++; else - glDisable (GL_DEPTH_TEST); + ctx->legacy_state_set--; } +/* XXX: This API has been deprecated */ gboolean cogl_get_depth_test_enabled (void) { - return glIsEnabled (GL_DEPTH_TEST) == GL_TRUE ? TRUE : FALSE; + _COGL_GET_CONTEXT (ctx, FALSE); + return ctx->legacy_depth_test_enabled; } void @@ -1136,3 +1139,10 @@ _cogl_transform_point (const CoglMatrix *matrix_mv, #undef VIEWPORT_TRANSFORM_X #undef VIEWPORT_TRANSFORM_Y + +GQuark +_cogl_error_quark (void) +{ + return g_quark_from_static_string ("cogl-error-quark"); +} + diff --git a/clutter/cogl/cogl/cogl.h b/clutter/cogl/cogl/cogl.h index e639658da..791e1ad5f 100644 --- a/clutter/cogl/cogl/cogl.h +++ b/clutter/cogl/cogl/cogl.h @@ -483,6 +483,9 @@ cogl_get_viewport (float v[4]); * order specified using clutter_actor_raise() and * clutter_actor_lower(), otherwise it will also take into account the * actor's depth. Depth testing is disabled by default. + * + * Deprecated: 1.4: Use cogl_material_set_depth_test_enabled() + * instead. */ void cogl_set_depth_test_enabled (gboolean setting); @@ -493,6 +496,9 @@ cogl_set_depth_test_enabled (gboolean setting); * Queries if depth testing has been enabled via cogl_set_depth_test_enable() * * Return value: %TRUE if depth testing is enabled, and %FALSE otherwise + * + * Deprecated: 1.4: Use cogl_material_get_depth_test_enabled() + * instead. */ gboolean cogl_get_depth_test_enabled (void); diff --git a/tests/conform/Makefile.am b/tests/conform/Makefile.am index e9d1e57a8..645a99786 100644 --- a/tests/conform/Makefile.am +++ b/tests/conform/Makefile.am @@ -31,6 +31,7 @@ test_conformance_SOURCES = \ test-cogl-pixel-buffer.c \ test-cogl-path.c \ test-cogl-object.c \ + test-cogl-depth-test.c \ test-path.c \ test-pick.c \ test-clutter-rectangle.c \ diff --git a/tests/conform/test-cogl-depth-test.c b/tests/conform/test-cogl-depth-test.c new file mode 100644 index 000000000..4cc36c68b --- /dev/null +++ b/tests/conform/test-cogl-depth-test.c @@ -0,0 +1,354 @@ + +#include + +#ifndef COGL_ENABLE_EXPERIMENTAL_API +#define COGL_ENABLE_EXPERIMENTAL_API +#endif +#include + +#include + +#include "test-conform-common.h" + +static const ClutterColor stage_color = { 0x0, 0x0, 0x0, 0xff }; + +#define QUAD_WIDTH 20 + +#define RED 0 +#define GREEN 1 +#define BLUE 2 +#define ALPHA 3 + +#define MASK_RED(COLOR) ((COLOR & 0xff000000) >> 24); +#define MASK_GREEN(COLOR) ((COLOR & 0xff0000) >> 16); +#define MASK_BLUE(COLOR) ((COLOR & 0xff00) >> 8); +#define MASK_ALPHA(COLOR) (COLOR & 0xff); + +typedef struct _TestState +{ + guint frame; + ClutterGeometry stage_geom; +} TestState; + +typedef struct +{ + guint32 color; + float depth; + gboolean test_enable; + CoglDepthTestFunction test_function; + gboolean write_enable; + float range_near; + float range_far; +} TestDepthState; + +static void +check_pixel (GLubyte *pixel, guint32 color) +{ + guint8 r = MASK_RED (color); + guint8 g = MASK_GREEN (color); + guint8 b = MASK_BLUE (color); + guint8 a = MASK_ALPHA (color); + + if (g_test_verbose ()) + g_print (" expected = %x, %x, %x, %x\n", + r, g, b, a); + /* FIXME - allow for hardware in-precision */ + g_assert_cmpint (pixel[RED], ==, r); + g_assert_cmpint (pixel[GREEN], ==, g); + g_assert_cmpint (pixel[BLUE], ==, b); + + /* FIXME + * We ignore the alpha, since we don't know if our render target is + * RGB or RGBA */ + /* g_assert (pixel[ALPHA] == a); */ +} + +static gboolean +draw_rectangle (TestState *state, + int x, + int y, + TestDepthState *rect_state) +{ + guint8 Cr = MASK_RED (rect_state->color); + guint8 Cg = MASK_GREEN (rect_state->color); + guint8 Cb = MASK_BLUE (rect_state->color); + guint8 Ca = MASK_ALPHA (rect_state->color); + CoglHandle material; + + material = cogl_material_new (); + cogl_material_set_depth_test_enabled (material, rect_state->test_enable); + cogl_material_set_depth_test_function (material, rect_state->test_function); + cogl_material_set_depth_writing_enabled (material, rect_state->write_enable); + if (!cogl_material_set_depth_range (material, + rect_state->range_near, + rect_state->range_far, + NULL)) + { + cogl_handle_unref (material); + return FALSE; + } + + cogl_material_set_color4ub (material, Cr, Cg, Cb, Ca); + + cogl_set_source (material); + + cogl_push_matrix (); + cogl_translate (0, 0, rect_state->depth); + cogl_rectangle (x * QUAD_WIDTH, + y * QUAD_WIDTH, + x * QUAD_WIDTH + QUAD_WIDTH, + y * QUAD_WIDTH + QUAD_WIDTH); + cogl_pop_matrix (); + + cogl_handle_unref (material); + + return TRUE; +} + +static void +test_depth (TestState *state, + int x, + int y, + TestDepthState *rect0_state, + TestDepthState *rect1_state, + TestDepthState *rect2_state, + guint32 expected_result) +{ + GLubyte pixel[4]; + GLint y_off; + GLint x_off; + gboolean missing_feature = FALSE; + + if (rect0_state) + missing_feature |= !draw_rectangle (state, x, y, rect0_state); + if (rect1_state) + missing_feature |= !draw_rectangle (state, x, y, rect1_state); + if (rect2_state) + missing_feature |= !draw_rectangle (state, x, y, rect2_state); + + /* We don't consider it an error that we can't test something + * the driver doesn't support. */ + if (missing_feature) + return; + + /* See what we got... */ + + y_off = y * QUAD_WIDTH + (QUAD_WIDTH / 2); + x_off = x * QUAD_WIDTH + (QUAD_WIDTH / 2); + + /* XXX: + * We haven't always had good luck with GL drivers implementing glReadPixels + * reliably and skipping the first two frames improves our chances... */ + if (state->frame <= 2) + return; + + cogl_read_pixels (x_off, y_off, 1, 1, + COGL_READ_PIXELS_COLOR_BUFFER, + COGL_PIXEL_FORMAT_RGBA_8888_PRE, + pixel); + + check_pixel (pixel, expected_result); +} + +static void +on_paint (ClutterActor *actor, TestState *state) +{ + int frame_num; + CoglMatrix projection_save; + CoglMatrix identity; + + /* We don't want the effects of perspective division to interfere + * with the positions of our test rectangles on the x and y axis + * so we use an orthographic projection... + */ + + cogl_get_projection_matrix (&projection_save); + + cogl_ortho (0, state->stage_geom.width, /* left, right */ + state->stage_geom.height, 0, /* bottom, top */ + -1, 100 /* z near, far */); + + cogl_push_matrix (); + cogl_matrix_init_identity (&identity); + cogl_set_modelview_matrix (&identity); + + /* Sanity check a few of the different depth test functions + * and that depth writing can be disabled... */ + + { + /* Closest */ + TestDepthState rect0_state = { + 0xff0000ff, /* rgba color */ + -10, /* depth */ + FALSE, /* depth test enable */ + COGL_DEPTH_TEST_FUNCTION_ALWAYS, + TRUE, /* depth write enable */ + 0, 1 /* depth range */ + }; + /* Furthest */ + TestDepthState rect1_state = { + 0x00ff00ff, /* rgba color */ + -70, /* depth */ + TRUE, /* depth test enable */ + COGL_DEPTH_TEST_FUNCTION_ALWAYS, + TRUE, /* depth write enable */ + 0, 1 /* depth range */ + }; + /* In the middle */ + TestDepthState rect2_state = { + 0x0000ffff, /* rgba color */ + -20, /* depth */ + TRUE, /* depth test enable */ + COGL_DEPTH_TEST_FUNCTION_NEVER, + TRUE, /* depth write enable */ + 0, 1 /* depth range */ + }; + + test_depth (state, 0, 0, /* position */ + &rect0_state, &rect1_state, &rect2_state, + 0x00ff00ff); /* expected */ + + rect2_state.test_function = COGL_DEPTH_TEST_FUNCTION_ALWAYS; + test_depth (state, 1, 0, /* position */ + &rect0_state, &rect1_state, &rect2_state, + 0x0000ffff); /* expected */ + + rect2_state.test_function = COGL_DEPTH_TEST_FUNCTION_LESS; + test_depth (state, 2, 0, /* position */ + &rect0_state, &rect1_state, &rect2_state, + 0x0000ffff); /* expected */ + + rect2_state.test_function = COGL_DEPTH_TEST_FUNCTION_GREATER; + test_depth (state, 3, 0, /* position */ + &rect0_state, &rect1_state, &rect2_state, + 0x00ff00ff); /* expected */ + + rect0_state.test_enable = TRUE; + rect1_state.write_enable = FALSE; + test_depth (state, 4, 0, /* position */ + &rect0_state, &rect1_state, &rect2_state, + 0x0000ffff); /* expected */ + } + + /* Check that the depth buffer values can be mapped into different + * ranges... */ + + { + /* Closest by depth, furthest by depth range */ + TestDepthState rect0_state = { + 0xff0000ff, /* rgba color */ + -10, /* depth */ + TRUE, /* depth test enable */ + COGL_DEPTH_TEST_FUNCTION_ALWAYS, + TRUE, /* depth write enable */ + 0.5, 1 /* depth range */ + }; + /* Furthest by depth, nearest by depth range */ + TestDepthState rect1_state = { + 0x00ff00ff, /* rgba color */ + -70, /* depth */ + TRUE, /* depth test enable */ + COGL_DEPTH_TEST_FUNCTION_GREATER, + TRUE, /* depth write enable */ + 0, 0.5 /* depth range */ + }; + + test_depth (state, 0, 1, /* position */ + &rect0_state, &rect1_state, NULL, + 0xff0000ff); /* expected */ + } + + /* Test that the legacy cogl_set_depth_test_enabled() API still + * works... */ + + { + /* Nearest */ + TestDepthState rect0_state = { + 0xff0000ff, /* rgba color */ + -10, /* depth */ + FALSE, /* depth test enable */ + COGL_DEPTH_TEST_FUNCTION_LESS, + TRUE, /* depth write enable */ + 0, 1 /* depth range */ + }; + /* Furthest */ + TestDepthState rect1_state = { + 0x00ff00ff, /* rgba color */ + -70, /* depth */ + FALSE, /* depth test enable */ + COGL_DEPTH_TEST_FUNCTION_LESS, + TRUE, /* depth write enable */ + 0, 1 /* depth range */ + }; + + cogl_set_depth_test_enabled (TRUE); + test_depth (state, 0, 2, /* position */ + &rect0_state, &rect1_state, NULL, + 0xff0000ff); /* expected */ + cogl_set_depth_test_enabled (FALSE); + test_depth (state, 1, 2, /* position */ + &rect0_state, &rect1_state, NULL, + 0x00ff00ff); /* expected */ + } + + cogl_pop_matrix (); + cogl_set_projection_matrix (&projection_save); + + /* 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: */ + frame_num = state->frame++; + if (frame_num < 2) + g_usleep (G_USEC_PER_SEC); + + /* Comment this out if you want visual feedback for what this test paints */ +#if 1 + if (frame_num == 3) + clutter_main_quit (); +#endif +} + +static gboolean +queue_redraw (gpointer stage) +{ + clutter_actor_queue_redraw (CLUTTER_ACTOR (stage)); + + return TRUE; +} + +void +test_cogl_depth_test (TestConformSimpleFixture *fixture, + gconstpointer data) +{ + TestState state; + ClutterActor *stage; + ClutterActor *group; + guint idle_source; + + state.frame = 0; + + stage = clutter_stage_get_default (); + + clutter_stage_set_color (CLUTTER_STAGE (stage), &stage_color); + clutter_actor_get_geometry (stage, &state.stage_geom); + + 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); + + if (g_test_verbose ()) + g_print ("OK\n"); +} + diff --git a/tests/conform/test-conform-main.c b/tests/conform/test-conform-main.c index e95c73db3..a2a48a422 100644 --- a/tests/conform/test-conform-main.c +++ b/tests/conform/test-conform-main.c @@ -198,6 +198,7 @@ main (int argc, char **argv) TEST_CONFORM_SIMPLE ("/cogl", test_cogl_premult); TEST_CONFORM_SIMPLE ("/cogl", test_cogl_readpixels); TEST_CONFORM_SIMPLE ("/cogl", test_cogl_path); + TEST_CONFORM_SIMPLE ("/cogl", test_cogl_depth_test); TEST_CONFORM_SIMPLE ("/cogl/texture", test_cogl_npot_texture); TEST_CONFORM_SIMPLE ("/cogl/texture", test_cogl_multitexture);