/*
 * Mesa 3-D graphics library
 *
 * Copyright (C) 2009  VMware, Inc.   All Rights Reserved.
 *
 * 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 (including the next
 * paragraph) 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.
 */

/**
 * \file condrender.c
 * Conditional rendering functions
 *
 * \author Brian Paul
 */

#include "glheader.h"
#include "condrender.h"
#include "enums.h"
#include "mtypes.h"
#include "queryobj.h"


static ALWAYS_INLINE void
begin_conditional_render(struct gl_context *ctx, GLuint queryId, GLenum mode,
                         bool no_error)
{
   struct gl_query_object *q = NULL;

   assert(ctx->Query.CondRenderMode == GL_NONE);

   if (queryId != 0)
      q = _mesa_lookup_query_object(ctx, queryId);

   if (!no_error) {
      /* Section 2.14 (Conditional Rendering) of the OpenGL 3.0 spec says:
       *
       *     "The error INVALID_VALUE is generated if <id> is not the name of an
       *     existing query object query."
       */
      if (!q) {
         _mesa_error(ctx, GL_INVALID_VALUE,
                     "glBeginConditionalRender(bad queryId=%u)", queryId);
         return;
      }
      assert(q->Id == queryId);

      switch (mode) {
      case GL_QUERY_WAIT:
      case GL_QUERY_NO_WAIT:
      case GL_QUERY_BY_REGION_WAIT:
      case GL_QUERY_BY_REGION_NO_WAIT:
         break; /* OK */
      case GL_QUERY_WAIT_INVERTED:
      case GL_QUERY_NO_WAIT_INVERTED:
      case GL_QUERY_BY_REGION_WAIT_INVERTED:
      case GL_QUERY_BY_REGION_NO_WAIT_INVERTED:
         if (ctx->Extensions.ARB_conditional_render_inverted)
            break; /* OK */
         /* fallthrough - invalid */
      default:
         _mesa_error(ctx, GL_INVALID_ENUM, "glBeginConditionalRender(mode=%s)",
                     _mesa_enum_to_string(mode));
         return;
      }

      /* Section 2.14 (Conditional Rendering) of the OpenGL 3.0 spec says:
       *
       *     "The error INVALID_OPERATION is generated if <id> is the name of a
       *     query object with a target other than SAMPLES_PASSED, or <id> is
       *     the name of a query currently in progress."
       */
      if ((q->Target != GL_SAMPLES_PASSED &&
           q->Target != GL_ANY_SAMPLES_PASSED &&
           q->Target != GL_ANY_SAMPLES_PASSED_CONSERVATIVE &&
           q->Target != GL_TRANSFORM_FEEDBACK_STREAM_OVERFLOW_ARB &&
           q->Target != GL_TRANSFORM_FEEDBACK_OVERFLOW_ARB) || q->Active) {
         _mesa_error(ctx, GL_INVALID_OPERATION, "glBeginConditionalRender()");
         return;
      }
   }

   ctx->Query.CondRenderQuery = q;
   ctx->Query.CondRenderMode = mode;

   if (ctx->Driver.BeginConditionalRender)
      ctx->Driver.BeginConditionalRender(ctx, q, mode);
}


void GLAPIENTRY
_mesa_BeginConditionalRender_no_error(GLuint queryId, GLenum mode)
{
   GET_CURRENT_CONTEXT(ctx);
   begin_conditional_render(ctx, queryId, mode, true);
}


void GLAPIENTRY
_mesa_BeginConditionalRender(GLuint queryId, GLenum mode)
{
   GET_CURRENT_CONTEXT(ctx);

   /* Section 2.14 (Conditional Rendering) of the OpenGL 3.0 spec says:
    *
    *     "If BeginConditionalRender is called while conditional rendering is
    *     in progress, or if EndConditionalRender is called while conditional
    *     rendering is not in progress, the error INVALID_OPERATION is
    *     generated."
    */
   if (!ctx->Extensions.NV_conditional_render || ctx->Query.CondRenderQuery) {
      _mesa_error(ctx, GL_INVALID_OPERATION, "glBeginConditionalRender()");
      return;
   }

   begin_conditional_render(ctx, queryId, mode, false);
}


static void
end_conditional_render(struct gl_context *ctx)
{
   FLUSH_VERTICES(ctx, 0x0);

   if (ctx->Driver.EndConditionalRender)
      ctx->Driver.EndConditionalRender(ctx, ctx->Query.CondRenderQuery);

   ctx->Query.CondRenderQuery = NULL;
   ctx->Query.CondRenderMode = GL_NONE;
}


void APIENTRY
_mesa_EndConditionalRender_no_error(void)
{
   GET_CURRENT_CONTEXT(ctx);
   end_conditional_render(ctx);
}


void APIENTRY
_mesa_EndConditionalRender(void)
{
   GET_CURRENT_CONTEXT(ctx);

   if (!ctx->Extensions.NV_conditional_render || !ctx->Query.CondRenderQuery) {
      _mesa_error(ctx, GL_INVALID_OPERATION, "glEndConditionalRender()");
      return;
   }

   end_conditional_render(ctx);
}


/**
 * This function is called by software rendering commands (all point,
 * line triangle drawing, glClear, glDrawPixels, glCopyPixels, and
 * glBitmap, glBlitFramebuffer) to determine if subsequent drawing
 * commands should be
 * executed or discarded depending on the current conditional
 * rendering state.  Ideally, this check would be implemented by the
 * GPU when doing hardware rendering.  XXX should this function be
 * called via a new driver hook?
 *
 * \return GL_TRUE if we should render, GL_FALSE if we should discard
 */
GLboolean
_mesa_check_conditional_render(struct gl_context *ctx)
{
   struct gl_query_object *q = ctx->Query.CondRenderQuery;

   if (!q) {
      /* no query in progress - draw normally */
      return GL_TRUE;
   }

   switch (ctx->Query.CondRenderMode) {
   case GL_QUERY_BY_REGION_WAIT:
      /* fall-through */
   case GL_QUERY_WAIT:
      if (!q->Ready) {
         ctx->Driver.WaitQuery(ctx, q);
      }
      return q->Result > 0;
   case GL_QUERY_BY_REGION_WAIT_INVERTED:
      /* fall-through */
   case GL_QUERY_WAIT_INVERTED:
      if (!q->Ready) {
         ctx->Driver.WaitQuery(ctx, q);
      }
      return q->Result == 0;
   case GL_QUERY_BY_REGION_NO_WAIT:
      /* fall-through */
   case GL_QUERY_NO_WAIT:
      if (!q->Ready)
         ctx->Driver.CheckQuery(ctx, q);
      return q->Ready ? (q->Result > 0) : GL_TRUE;
   case GL_QUERY_BY_REGION_NO_WAIT_INVERTED:
      /* fall-through */
   case GL_QUERY_NO_WAIT_INVERTED:
      if (!q->Ready)
         ctx->Driver.CheckQuery(ctx, q);
      return q->Ready ? (q->Result == 0) : GL_TRUE;
   default:
      _mesa_problem(ctx, "Bad cond render mode %s in "
                    " _mesa_check_conditional_render()",
                    _mesa_enum_to_string(ctx->Query.CondRenderMode));
      return GL_TRUE;
   }
}