/* SPDX-FileCopyrightText: 2023 Blender Authors
 *
 * SPDX-License-Identifier: GPL-2.0-or-later */

/** \file
 * \ingroup bpygpu
 *
 * - Use `bpygpu_` for local API.
 * - Use `BPyGPU` for public API.
 */

#include <Python.h>

#include "GPU_capabilities.hh"
#include "GPU_compute.hh"
#include "GPU_state.hh"

#include "../generic/python_compat.hh" /* IWYU pragma: keep. */

#include "gpu_py.hh"
#include "gpu_py_compute.hh" /* own include */
#include "gpu_py_shader.hh"

PyDoc_STRVAR(
    /* Wrap. */
    pygpu_compute_dispatch_doc,
    ".. function:: dispatch(shader, groups_x_len,  groups_y_len,  groups_z_len)\n"
    "\n"
    "   Dispatches GPU compute.\n"
    "\n"
    "   :arg shader: The shader that you want to dispatch.\n"
    "   :type shader: :class:`gpu.types.GPUShader`\n"
    "   :arg groups_x_len: Int for group x length:\n"
    "   :type groups_x_len: int\n"
    "   :arg groups_y_len: Int for group y length:\n"
    "   :type groups_y_len: int\n"
    "   :arg groups_z_len: Int for group z length:\n"
    "   :type groups_z_len: int\n"
    "   :return: Shader object.\n"
    "   :rtype: :class:`gpu.types.GPUShader`\n");
static PyObject *pygpu_compute_dispatch(PyObject * /*self*/, PyObject *args, PyObject *kwds)
{
  BPYGPU_IS_INIT_OR_ERROR_OBJ;

  BPyGPUShader *py_shader;
  int groups_x_len;
  int groups_y_len;
  int groups_z_len;

  static const char *_keywords[] = {
      "shader", "groups_x_len", "groups_y_len", "groups_z_len", nullptr};
  static _PyArg_Parser _parser = {
        PY_ARG_PARSER_HEAD_COMPAT()
        "O" /* `shader` */
        "i" /* `groups_x_len` */
        "i" /* `groups_y_len` */
        "i" /* `groups_z_len` */
        ":dispatch",
        _keywords,
        nullptr,
    };
  if (_PyArg_ParseTupleAndKeywordsFast(
          args, kwds, &_parser, &py_shader, &groups_x_len, &groups_y_len, &groups_z_len))
  {
    if (!BPyGPUShader_Check(py_shader)) {
      PyErr_Format(PyExc_TypeError, "Expected a GPUShader, got %s", Py_TYPE(py_shader)->tp_name);
      return nullptr;
    }

    /* Check that groups do not exceed #GPU_max_work_group_count(). */
    const int max_work_group_count_x = GPU_max_work_group_count(0);
    const int max_work_group_count_y = GPU_max_work_group_count(1);
    const int max_work_group_count_z = GPU_max_work_group_count(2);

    /* Report back to the user both the requested and the maximum supported value. */
    if (groups_x_len > GPU_max_work_group_count(0)) {
      PyErr_Format(PyExc_ValueError,
                   "groups_x_len (%d) exceeds maximum supported value (max work group count: %d)",
                   groups_x_len,
                   max_work_group_count_x);
      return nullptr;
    }
    if (groups_y_len > GPU_max_work_group_count(1)) {
      PyErr_Format(PyExc_ValueError,
                   "groups_y_len (%d) exceeds maximum supported value (max work group count: %d)",
                   groups_y_len,
                   max_work_group_count_y);
      return nullptr;
    }
    if (groups_z_len > GPU_max_work_group_count(2)) {
      PyErr_Format(PyExc_ValueError,
                   "groups_z_len (%d) exceeds maximum supported value (max work group count: %d)",
                   groups_z_len,
                   max_work_group_count_z);
      return nullptr;
    }

    GPUShader *shader = py_shader->shader;
    GPU_compute_dispatch(shader, groups_x_len, groups_y_len, groups_z_len);
    GPU_memory_barrier(GPU_BARRIER_TEXTURE_FETCH | GPU_BARRIER_SHADER_IMAGE_ACCESS);
  }
  Py_RETURN_NONE;
}

/* -------------------------------------------------------------------- */
/** \name Module
 * \{ */

#ifdef __GNUC__
#  ifdef __clang__
#    pragma clang diagnostic push
#    pragma clang diagnostic ignored "-Wcast-function-type"
#  else
#    pragma GCC diagnostic push
#    pragma GCC diagnostic ignored "-Wcast-function-type"
#  endif
#endif

static PyMethodDef pygpu_compute__tp_methods[] = {
    {"dispatch",
     (PyCFunction)pygpu_compute_dispatch,
     METH_VARARGS | METH_KEYWORDS,
     pygpu_compute_dispatch_doc},
    {nullptr, nullptr, 0, nullptr},
};

#ifdef __GNUC__
#  ifdef __clang__
#    pragma clang diagnostic pop
#  else
#    pragma GCC diagnostic pop
#  endif
#endif

PyDoc_STRVAR(
    /* Wrap. */
    pygpu_compute__tp_doc,
    "This module provides access to the global GPU compute functions");
static PyModuleDef pygpu_compute_module_def = {
    /*m_base*/ PyModuleDef_HEAD_INIT,
    /*m_name*/ "gpu.compute",
    /*m_doc*/ pygpu_compute__tp_doc,
    /*m_size*/ 0,
    /*m_methods*/ pygpu_compute__tp_methods,
    /*m_slots*/ nullptr,
    /*m_traverse*/ nullptr,
    /*m_clear*/ nullptr,
    /*m_free*/ nullptr,
};

PyObject *bpygpu_compute_init()
{
  PyObject *submodule;

  submodule = PyModule_Create(&pygpu_compute_module_def);

  return submodule;
}

/** \} */
