/*
 * Copyright © 2014 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 (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.
 *
 * Authors:
 *    Neil Roberts <neil@linux.intel.com>
 */

/** @file brw_conditional_render.c
 *
 * Support for conditional rendering based on query objects
 * (GL_NV_conditional_render, GL_ARB_conditional_render_inverted) on Gen7+.
 */

#include "main/imports.h"
#include "main/condrender.h"

#include "brw_context.h"
#include "brw_defines.h"
#include "intel_batchbuffer.h"

static void
set_predicate_enable(struct brw_context *brw,
                     bool value)
{
   if (value)
      brw->predicate.state = BRW_PREDICATE_STATE_RENDER;
   else
      brw->predicate.state = BRW_PREDICATE_STATE_DONT_RENDER;
}

static void
set_predicate_for_overflow_query(struct brw_context *brw,
                                 struct brw_query_object *query,
                                 int stream_start, int count)
{
   if (!can_do_mi_math_and_lrr(brw->screen)) {
      brw->predicate.state = BRW_PREDICATE_STATE_STALL_FOR_QUERY;
      return;
   }

   brw->predicate.state = BRW_PREDICATE_STATE_USE_BIT;

   /* Needed to ensure the memory is coherent for the MI_LOAD_REGISTER_MEM
    * command when loading the values into the predicate source registers for
    * conditional rendering.
    */
   brw_emit_pipe_control_flush(brw, PIPE_CONTROL_FLUSH_ENABLE);

   hsw_overflow_result_to_gpr0(brw, query, count);
   brw_load_register_reg64(brw, HSW_CS_GPR(0), MI_PREDICATE_SRC0);
   brw_load_register_imm64(brw, MI_PREDICATE_SRC1, 0ull);
}

static void
set_predicate_for_occlusion_query(struct brw_context *brw,
                                  struct brw_query_object *query)
{
   if (!brw->predicate.supported) {
      brw->predicate.state = BRW_PREDICATE_STATE_STALL_FOR_QUERY;
      return;
   }

   brw->predicate.state = BRW_PREDICATE_STATE_USE_BIT;

   /* Needed to ensure the memory is coherent for the MI_LOAD_REGISTER_MEM
    * command when loading the values into the predicate source registers for
    * conditional rendering.
    */
   brw_emit_pipe_control_flush(brw, PIPE_CONTROL_FLUSH_ENABLE);

   brw_load_register_mem64(brw, MI_PREDICATE_SRC0, query->bo, 0 /* offset */);
   brw_load_register_mem64(brw, MI_PREDICATE_SRC1, query->bo, 8 /* offset */);
}

static void
set_predicate_for_result(struct brw_context *brw,
                         struct brw_query_object *query,
                         bool inverted)
{
   int load_op;

   assert(query->bo != NULL);

   switch (query->Base.Target) {
   case GL_TRANSFORM_FEEDBACK_STREAM_OVERFLOW_ARB:
      set_predicate_for_overflow_query(brw, query, 0, 1);
      break;
   case GL_TRANSFORM_FEEDBACK_OVERFLOW_ARB:
      set_predicate_for_overflow_query(brw, query, 0, MAX_VERTEX_STREAMS);
      break;
   default:
      set_predicate_for_occlusion_query(brw, query);
   }

   if (brw->predicate.state == BRW_PREDICATE_STATE_USE_BIT) {
      if (inverted)
         load_op = MI_PREDICATE_LOADOP_LOAD;
      else
         load_op = MI_PREDICATE_LOADOP_LOADINV;

      BEGIN_BATCH(1);
      OUT_BATCH(GEN7_MI_PREDICATE |
                load_op |
                MI_PREDICATE_COMBINEOP_SET |
                MI_PREDICATE_COMPAREOP_SRCS_EQUAL);
      ADVANCE_BATCH();
   }
}

static void
brw_begin_conditional_render(struct gl_context *ctx,
                             struct gl_query_object *q,
                             GLenum mode)
{
   struct brw_context *brw = brw_context(ctx);
   struct brw_query_object *query = (struct brw_query_object *) q;
   bool inverted;

   switch (mode) {
   case GL_QUERY_WAIT:
   case GL_QUERY_NO_WAIT:
   case GL_QUERY_BY_REGION_WAIT:
   case GL_QUERY_BY_REGION_NO_WAIT:
      inverted = false;
      break;
   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:
      inverted = true;
      break;
   default:
      unreachable("Unexpected conditional render mode");
   }

   /* If there are already samples from a BLT operation or if the query object
    * is ready then we can avoid looking at the values in the buffer and just
    * decide whether to draw using the CPU without stalling.
    */
   if (query->Base.Result || query->Base.Ready)
      set_predicate_enable(brw, (query->Base.Result != 0) ^ inverted);
   else
      set_predicate_for_result(brw, query, inverted);
}

static void
brw_end_conditional_render(struct gl_context *ctx,
                           struct gl_query_object *q)
{
   struct brw_context *brw = brw_context(ctx);

   /* When there is no longer a conditional render in progress it should
    * always render.
    */
   brw->predicate.state = BRW_PREDICATE_STATE_RENDER;
}

void
brw_init_conditional_render_functions(struct dd_function_table *functions)
{
   functions->BeginConditionalRender = brw_begin_conditional_render;
   functions->EndConditionalRender = brw_end_conditional_render;
}

bool
brw_check_conditional_render(struct brw_context *brw)
{
   if (brw->predicate.state == BRW_PREDICATE_STATE_STALL_FOR_QUERY) {
      perf_debug("Conditional rendering is implemented in software and may "
                 "stall.\n");
      return _mesa_check_conditional_render(&brw->ctx);
   }

   return brw->predicate.state != BRW_PREDICATE_STATE_DONT_RENDER;
}