/* SPDX-FileCopyrightText: 2015 Pixar
 *
 * SPDX-License-Identifier: Apache-2.0 */

#include <epoxy/gl.h>

/* There are few aspects here:
 *   - macOS is strict about including both gl.h and gl3.h
 *   - libepoxy only pretends to be a replacement for gl.h
 *   - OpenSubdiv internally uses `OpenGL/gl3.h` on macOS
 *
 * In order to silence the warning pretend that gl3 has been included, fully relying on symbols
 * from the epoxy.
 *
 * This works differently from how OpenSubdiv internally will use `OpenGL/gl3.h` without epoxy.
 * Sounds fragile, but so far things seems to work. */
#if defined(__APPLE__)
#  define __gl3_h_
#endif

#include "gl_compute_evaluator.h"

#include <opensubdiv/far/error.h>
#include <opensubdiv/far/patchDescriptor.h>
#include <opensubdiv/far/stencilTable.h>
#include <opensubdiv/osd/glslPatchShaderSource.h>

#include <cassert>
#include <cmath>
#include <sstream>
#include <string>
#include <vector>

using OpenSubdiv::Far::LimitStencilTable;
using OpenSubdiv::Far::StencilTable;
using OpenSubdiv::Osd::BufferDescriptor;
using OpenSubdiv::Osd::PatchArray;
using OpenSubdiv::Osd::PatchArrayVector;

extern "C" char datatoc_glsl_compute_kernel_glsl[];

namespace blender::opensubdiv {

template<class T> GLuint createSSBO(std::vector<T> const &src)
{
  if (src.empty()) {
    return 0;
  }

  GLuint devicePtr = 0;

#if defined(GL_ARB_direct_state_access)
  if (epoxy_has_gl_extension("GL_ARB_direct_state_access")) {
    glCreateBuffers(1, &devicePtr);
    glNamedBufferData(devicePtr, src.size() * sizeof(T), &src.at(0), GL_STATIC_DRAW);
  }
  else
#endif
  {
    GLint prev = 0;
    glGetIntegerv(GL_SHADER_STORAGE_BUFFER_BINDING, &prev);
    glGenBuffers(1, &devicePtr);
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, devicePtr);
    glBufferData(GL_SHADER_STORAGE_BUFFER, src.size() * sizeof(T), &src.at(0), GL_STATIC_DRAW);
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, prev);
  }

  return devicePtr;
}

GLStencilTableSSBO::GLStencilTableSSBO(StencilTable const *stencilTable)
{
  _numStencils = stencilTable->GetNumStencils();
  if (_numStencils > 0) {
    _sizes = createSSBO(stencilTable->GetSizes());
    _offsets = createSSBO(stencilTable->GetOffsets());
    _indices = createSSBO(stencilTable->GetControlIndices());
    _weights = createSSBO(stencilTable->GetWeights());
    _duWeights = _dvWeights = 0;
    _duuWeights = _duvWeights = _dvvWeights = 0;
  }
  else {
    _sizes = _offsets = _indices = _weights = 0;
    _duWeights = _dvWeights = 0;
    _duuWeights = _duvWeights = _dvvWeights = 0;
  }
}

GLStencilTableSSBO::GLStencilTableSSBO(LimitStencilTable const *limitStencilTable)
{
  _numStencils = limitStencilTable->GetNumStencils();
  if (_numStencils > 0) {
    _sizes = createSSBO(limitStencilTable->GetSizes());
    _offsets = createSSBO(limitStencilTable->GetOffsets());
    _indices = createSSBO(limitStencilTable->GetControlIndices());
    _weights = createSSBO(limitStencilTable->GetWeights());
    _duWeights = createSSBO(limitStencilTable->GetDuWeights());
    _dvWeights = createSSBO(limitStencilTable->GetDvWeights());
    _duuWeights = createSSBO(limitStencilTable->GetDuuWeights());
    _duvWeights = createSSBO(limitStencilTable->GetDuvWeights());
    _dvvWeights = createSSBO(limitStencilTable->GetDvvWeights());
  }
  else {
    _sizes = _offsets = _indices = _weights = 0;
    _duWeights = _dvWeights = 0;
    _duuWeights = _duvWeights = _dvvWeights = 0;
  }
}

GLStencilTableSSBO::~GLStencilTableSSBO()
{
  if (_sizes) {
    glDeleteBuffers(1, &_sizes);
  }
  if (_offsets) {
    glDeleteBuffers(1, &_offsets);
  }
  if (_indices) {
    glDeleteBuffers(1, &_indices);
  }
  if (_weights) {
    glDeleteBuffers(1, &_weights);
  }
  if (_duWeights) {
    glDeleteBuffers(1, &_duWeights);
  }
  if (_dvWeights) {
    glDeleteBuffers(1, &_dvWeights);
  }
  if (_duuWeights) {
    glDeleteBuffers(1, &_duuWeights);
  }
  if (_duvWeights) {
    glDeleteBuffers(1, &_duvWeights);
  }
  if (_dvvWeights) {
    glDeleteBuffers(1, &_dvvWeights);
  }
}

// ---------------------------------------------------------------------------

GLComputeEvaluator::GLComputeEvaluator() : _workGroupSize(64), _patchArraysSSBO(0)
{
  memset((void *)&_stencilKernel, 0, sizeof(_stencilKernel));
  memset((void *)&_patchKernel, 0, sizeof(_patchKernel));
}

GLComputeEvaluator::~GLComputeEvaluator()
{
  if (_patchArraysSSBO) {
    glDeleteBuffers(1, &_patchArraysSSBO);
  }
}

static GLuint compileKernel(BufferDescriptor const &srcDesc,
                            BufferDescriptor const &dstDesc,
                            BufferDescriptor const &duDesc,
                            BufferDescriptor const &dvDesc,
                            BufferDescriptor const &duuDesc,
                            BufferDescriptor const &duvDesc,
                            BufferDescriptor const &dvvDesc,
                            const char *kernelDefine,
                            int workGroupSize)
{
  GLuint program = glCreateProgram();

  GLuint shader = glCreateShader(GL_COMPUTE_SHADER);

  std::string patchBasisShaderSource =
      OpenSubdiv::Osd::GLSLPatchShaderSource::GetPatchBasisShaderSource();
  const char *patchBasisShaderSourceDefine = "#define OSD_PATCH_BASIS_GLSL\n";

  std::ostringstream defines;
  defines << "#define LENGTH " << srcDesc.length << "\n"
          << "#define SRC_STRIDE " << srcDesc.stride << "\n"
          << "#define DST_STRIDE " << dstDesc.stride << "\n"
          << "#define WORK_GROUP_SIZE " << workGroupSize << "\n"
          << kernelDefine << "\n"
          << patchBasisShaderSourceDefine << "\n";

  bool deriv1 = (duDesc.length > 0 || dvDesc.length > 0);
  bool deriv2 = (duuDesc.length > 0 || duvDesc.length > 0 || dvvDesc.length > 0);
  if (deriv1) {
    defines << "#define OPENSUBDIV_GLSL_COMPUTE_USE_1ST_DERIVATIVES\n";
  }
  if (deriv2) {
    defines << "#define OPENSUBDIV_GLSL_COMPUTE_USE_2ND_DERIVATIVES\n";
  }

  std::string defineStr = defines.str();

  const char *shaderSources[4] = {"#version 430\n", 0, 0, 0};

  shaderSources[1] = defineStr.c_str();
  shaderSources[2] = patchBasisShaderSource.c_str();
  shaderSources[3] = datatoc_glsl_compute_kernel_glsl;
  glShaderSource(shader, 4, shaderSources, NULL);
  glCompileShader(shader);
  glAttachShader(program, shader);

  GLint linked = 0;
  glLinkProgram(program);
  glGetProgramiv(program, GL_LINK_STATUS, &linked);

  if (linked == GL_FALSE) {
    char buffer[1024];
    glGetShaderInfoLog(shader, 1024, NULL, buffer);
    OpenSubdiv::Far::Error(OpenSubdiv::Far::FAR_RUNTIME_ERROR, buffer);

    glGetProgramInfoLog(program, 1024, NULL, buffer);
    OpenSubdiv::Far::Error(OpenSubdiv::Far::FAR_RUNTIME_ERROR, buffer);

    glDeleteProgram(program);
    return 0;
  }

  glDeleteShader(shader);

  return program;
}

bool GLComputeEvaluator::Compile(BufferDescriptor const &srcDesc,
                                 BufferDescriptor const &dstDesc,
                                 BufferDescriptor const &duDesc,
                                 BufferDescriptor const &dvDesc,
                                 BufferDescriptor const &duuDesc,
                                 BufferDescriptor const &duvDesc,
                                 BufferDescriptor const &dvvDesc)
{

  // create a stencil kernel
  if (!_stencilKernel.Compile(
          srcDesc, dstDesc, duDesc, dvDesc, duuDesc, duvDesc, dvvDesc, _workGroupSize))
  {
    return false;
  }

  // create a patch kernel
  if (!_patchKernel.Compile(
          srcDesc, dstDesc, duDesc, dvDesc, duuDesc, duvDesc, dvvDesc, _workGroupSize))
  {
    return false;
  }

  // create a patch arrays buffer
  if (!_patchArraysSSBO) {
    glGenBuffers(1, &_patchArraysSSBO);
  }

  return true;
}

/* static */
void GLComputeEvaluator::Synchronize(void * /*kernel*/)
{
  // XXX: this is currently just for the performance measuring purpose.
  // need to be reimplemented by fence and sync.
  glFinish();
}

int GLComputeEvaluator::GetDispatchSize(int count) const
{
  return (count + _workGroupSize - 1) / _workGroupSize;
}

void GLComputeEvaluator::DispatchCompute(int totalDispatchSize) const
{
  int maxWorkGroupCount[2] = {0, 0};

  glGetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_COUNT, 0, &maxWorkGroupCount[0]);
  glGetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_COUNT, 1, &maxWorkGroupCount[1]);

  const GLuint maxResX = static_cast<GLuint>(maxWorkGroupCount[0]);

  const int dispatchSize = GetDispatchSize(totalDispatchSize);
  GLuint dispatchRX = static_cast<GLuint>(dispatchSize);
  GLuint dispatchRY = 1u;
  if (dispatchRX > maxResX) {
    /* Since there are some limitations with regards to the maximum work group size (could be as
     * low as 64k elements per call), we split the number elements into a "2d" number, with the
     * final index being computed as `res_x + res_y * max_work_group_size`. Even with a maximum
     * work group size of 64k, that still leaves us with roughly `64k * 64k = 4` billion elements
     * total, which should be enough. If not, we could also use the 3rd dimension. */
    /* TODO(fclem): We could dispatch fewer groups if we compute the prime factorization and
     * get the smallest rect fitting the requirements. */
    dispatchRX = dispatchRY = std::ceil(std::sqrt(dispatchSize));
    /* Avoid a completely empty dispatch line caused by rounding. */
    if ((dispatchRX * (dispatchRY - 1)) >= dispatchSize) {
      dispatchRY -= 1;
    }
  }

  /* X and Y dimensions may have different limits so the above computation may not be right, but
   * even with the standard 64k minimum on all dimensions we still have a lot of room. Therefore,
   * we presume it all fits. */
  assert(dispatchRY < static_cast<GLuint>(maxWorkGroupCount[1]));

  glDispatchCompute(dispatchRX, dispatchRY, 1);
}

bool GLComputeEvaluator::EvalStencils(GLuint srcBuffer,
                                      BufferDescriptor const &srcDesc,
                                      GLuint dstBuffer,
                                      BufferDescriptor const &dstDesc,
                                      GLuint duBuffer,
                                      BufferDescriptor const &duDesc,
                                      GLuint dvBuffer,
                                      BufferDescriptor const &dvDesc,
                                      GLuint sizesBuffer,
                                      GLuint offsetsBuffer,
                                      GLuint indicesBuffer,
                                      GLuint weightsBuffer,
                                      GLuint duWeightsBuffer,
                                      GLuint dvWeightsBuffer,
                                      int start,
                                      int end) const
{

  return EvalStencils(srcBuffer,
                      srcDesc,
                      dstBuffer,
                      dstDesc,
                      duBuffer,
                      duDesc,
                      dvBuffer,
                      dvDesc,
                      0,
                      BufferDescriptor(),
                      0,
                      BufferDescriptor(),
                      0,
                      BufferDescriptor(),
                      sizesBuffer,
                      offsetsBuffer,
                      indicesBuffer,
                      weightsBuffer,
                      duWeightsBuffer,
                      dvWeightsBuffer,
                      0,
                      0,
                      0,
                      start,
                      end);
}

bool GLComputeEvaluator::EvalStencils(GLuint srcBuffer,
                                      BufferDescriptor const &srcDesc,
                                      GLuint dstBuffer,
                                      BufferDescriptor const &dstDesc,
                                      GLuint duBuffer,
                                      BufferDescriptor const &duDesc,
                                      GLuint dvBuffer,
                                      BufferDescriptor const &dvDesc,
                                      GLuint duuBuffer,
                                      BufferDescriptor const &duuDesc,
                                      GLuint duvBuffer,
                                      BufferDescriptor const &duvDesc,
                                      GLuint dvvBuffer,
                                      BufferDescriptor const &dvvDesc,
                                      GLuint sizesBuffer,
                                      GLuint offsetsBuffer,
                                      GLuint indicesBuffer,
                                      GLuint weightsBuffer,
                                      GLuint duWeightsBuffer,
                                      GLuint dvWeightsBuffer,
                                      GLuint duuWeightsBuffer,
                                      GLuint duvWeightsBuffer,
                                      GLuint dvvWeightsBuffer,
                                      int start,
                                      int end) const
{

  if (!_stencilKernel.program) {
    return false;
  }
  int count = end - start;
  if (count <= 0) {
    return true;
  }

  glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, srcBuffer);
  glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, dstBuffer);
  glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, duBuffer);
  glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, dvBuffer);
  glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 10, duuBuffer);
  glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 11, duvBuffer);
  glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 12, dvvBuffer);
  glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 4, sizesBuffer);
  glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 5, offsetsBuffer);
  glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 6, indicesBuffer);
  glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 7, weightsBuffer);
  if (duWeightsBuffer) {
    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 8, duWeightsBuffer);
  }
  if (dvWeightsBuffer) {
    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 9, dvWeightsBuffer);
  }
  if (duuWeightsBuffer) {
    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 13, duuWeightsBuffer);
  }
  if (duvWeightsBuffer) {
    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 14, duvWeightsBuffer);
  }
  if (dvvWeightsBuffer) {
    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 15, dvvWeightsBuffer);
  }

  GLint activeProgram;
  glGetIntegerv(GL_CURRENT_PROGRAM, &activeProgram);
  glUseProgram(_stencilKernel.program);

  glUniform1i(_stencilKernel.uniformStart, start);
  glUniform1i(_stencilKernel.uniformEnd, end);
  glUniform1i(_stencilKernel.uniformSrcOffset, srcDesc.offset);
  glUniform1i(_stencilKernel.uniformDstOffset, dstDesc.offset);
  if (_stencilKernel.uniformDuDesc > 0) {
    glUniform3i(_stencilKernel.uniformDuDesc, duDesc.offset, duDesc.length, duDesc.stride);
  }
  if (_stencilKernel.uniformDvDesc > 0) {
    glUniform3i(_stencilKernel.uniformDvDesc, dvDesc.offset, dvDesc.length, dvDesc.stride);
  }
  if (_stencilKernel.uniformDuuDesc > 0) {
    glUniform3i(_stencilKernel.uniformDuuDesc, duuDesc.offset, duuDesc.length, duuDesc.stride);
  }
  if (_stencilKernel.uniformDuvDesc > 0) {
    glUniform3i(_stencilKernel.uniformDuvDesc, duvDesc.offset, duvDesc.length, duvDesc.stride);
  }
  if (_stencilKernel.uniformDvvDesc > 0) {
    glUniform3i(_stencilKernel.uniformDvvDesc, dvvDesc.offset, dvvDesc.length, dvvDesc.stride);
  }

  DispatchCompute(count);

  glUseProgram(activeProgram);

  glMemoryBarrier(GL_TEXTURE_FETCH_BARRIER_BIT);
  for (int i = 0; i < 16; ++i) {
    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, i, 0);
  }

  return true;
}

bool GLComputeEvaluator::EvalPatches(GLuint srcBuffer,
                                     BufferDescriptor const &srcDesc,
                                     GLuint dstBuffer,
                                     BufferDescriptor const &dstDesc,
                                     GLuint duBuffer,
                                     BufferDescriptor const &duDesc,
                                     GLuint dvBuffer,
                                     BufferDescriptor const &dvDesc,
                                     int numPatchCoords,
                                     GLuint patchCoordsBuffer,
                                     const PatchArrayVector &patchArrays,
                                     GLuint patchIndexBuffer,
                                     GLuint patchParamsBuffer) const
{

  return EvalPatches(srcBuffer,
                     srcDesc,
                     dstBuffer,
                     dstDesc,
                     duBuffer,
                     duDesc,
                     dvBuffer,
                     dvDesc,
                     0,
                     BufferDescriptor(),
                     0,
                     BufferDescriptor(),
                     0,
                     BufferDescriptor(),
                     numPatchCoords,
                     patchCoordsBuffer,
                     patchArrays,
                     patchIndexBuffer,
                     patchParamsBuffer);
}

bool GLComputeEvaluator::EvalPatches(GLuint srcBuffer,
                                     BufferDescriptor const &srcDesc,
                                     GLuint dstBuffer,
                                     BufferDescriptor const &dstDesc,
                                     GLuint duBuffer,
                                     BufferDescriptor const &duDesc,
                                     GLuint dvBuffer,
                                     BufferDescriptor const &dvDesc,
                                     GLuint duuBuffer,
                                     BufferDescriptor const &duuDesc,
                                     GLuint duvBuffer,
                                     BufferDescriptor const &duvDesc,
                                     GLuint dvvBuffer,
                                     BufferDescriptor const &dvvDesc,
                                     int numPatchCoords,
                                     GLuint patchCoordsBuffer,
                                     const PatchArrayVector &patchArrays,
                                     GLuint patchIndexBuffer,
                                     GLuint patchParamsBuffer) const
{

  if (!_patchKernel.program) {
    return false;
  }

  glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, srcBuffer);
  glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, dstBuffer);
  glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, duBuffer);
  glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, dvBuffer);
  glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 10, duuBuffer);
  glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 11, duvBuffer);
  glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 12, dvvBuffer);
  glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 5, patchCoordsBuffer);
  glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 6, patchIndexBuffer);
  glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 7, patchParamsBuffer);

  GLint activeProgram;
  glGetIntegerv(GL_CURRENT_PROGRAM, &activeProgram);
  glUseProgram(_patchKernel.program);

  glUniform1i(_patchKernel.uniformSrcOffset, srcDesc.offset);
  glUniform1i(_patchKernel.uniformDstOffset, dstDesc.offset);

  int patchArraySize = sizeof(PatchArray);
  glBindBuffer(GL_SHADER_STORAGE_BUFFER, _patchArraysSSBO);
  glBufferData(
      GL_SHADER_STORAGE_BUFFER, patchArrays.size() * patchArraySize, NULL, GL_STATIC_DRAW);
  for (int i = 0; i < (int)patchArrays.size(); ++i) {
    glBufferSubData(
        GL_SHADER_STORAGE_BUFFER, i * patchArraySize, sizeof(PatchArray), &patchArrays[i]);
  }
  glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 4, _patchArraysSSBO);

  if (_patchKernel.uniformDuDesc > 0) {
    glUniform3i(_patchKernel.uniformDuDesc, duDesc.offset, duDesc.length, duDesc.stride);
  }
  if (_patchKernel.uniformDvDesc > 0) {
    glUniform3i(_patchKernel.uniformDvDesc, dvDesc.offset, dvDesc.length, dvDesc.stride);
  }
  if (_patchKernel.uniformDuuDesc > 0) {
    glUniform3i(_patchKernel.uniformDuuDesc, duuDesc.offset, duuDesc.length, duuDesc.stride);
  }
  if (_patchKernel.uniformDuvDesc > 0) {
    glUniform3i(_patchKernel.uniformDuvDesc, duvDesc.offset, duvDesc.length, duvDesc.stride);
  }
  if (_patchKernel.uniformDvvDesc > 0) {
    glUniform3i(_patchKernel.uniformDvvDesc, dvvDesc.offset, dvvDesc.length, dvvDesc.stride);
  }

  DispatchCompute(numPatchCoords);

  glUseProgram(activeProgram);

  glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, 0);
  glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, 0);
  glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, 0);
  glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, 0);
  glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 4, 0);
  glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 5, 0);
  glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 6, 0);

  glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 10, 0);
  glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 11, 0);
  glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 12, 0);

  return true;
}
// ---------------------------------------------------------------------------

GLComputeEvaluator::_StencilKernel::_StencilKernel() : program(0) {}
GLComputeEvaluator::_StencilKernel::~_StencilKernel()
{
  if (program) {
    glDeleteProgram(program);
  }
}

bool GLComputeEvaluator::_StencilKernel::Compile(BufferDescriptor const &srcDesc,
                                                 BufferDescriptor const &dstDesc,
                                                 BufferDescriptor const &duDesc,
                                                 BufferDescriptor const &dvDesc,
                                                 BufferDescriptor const &duuDesc,
                                                 BufferDescriptor const &duvDesc,
                                                 BufferDescriptor const &dvvDesc,
                                                 int workGroupSize)
{
  // create stencil kernel
  if (program) {
    glDeleteProgram(program);
  }

  const char *kernelDefine = "#define OPENSUBDIV_GLSL_COMPUTE_KERNEL_EVAL_STENCILS\n";

  program = compileKernel(
      srcDesc, dstDesc, duDesc, dvDesc, duuDesc, duvDesc, dvvDesc, kernelDefine, workGroupSize);
  if (program == 0) {
    return false;
  }

  // cache uniform locations (TODO: use uniform block)
  uniformStart = glGetUniformLocation(program, "batchStart");
  uniformEnd = glGetUniformLocation(program, "batchEnd");
  uniformSrcOffset = glGetUniformLocation(program, "srcOffset");
  uniformDstOffset = glGetUniformLocation(program, "dstOffset");
  uniformDuDesc = glGetUniformLocation(program, "duDesc");
  uniformDvDesc = glGetUniformLocation(program, "dvDesc");
  uniformDuuDesc = glGetUniformLocation(program, "duuDesc");
  uniformDuvDesc = glGetUniformLocation(program, "duvDesc");
  uniformDvvDesc = glGetUniformLocation(program, "dvvDesc");

  return true;
}

// ---------------------------------------------------------------------------

GLComputeEvaluator::_PatchKernel::_PatchKernel() : program(0) {}
GLComputeEvaluator::_PatchKernel::~_PatchKernel()
{
  if (program) {
    glDeleteProgram(program);
  }
}

bool GLComputeEvaluator::_PatchKernel::Compile(BufferDescriptor const &srcDesc,
                                               BufferDescriptor const &dstDesc,
                                               BufferDescriptor const &duDesc,
                                               BufferDescriptor const &dvDesc,
                                               BufferDescriptor const &duuDesc,
                                               BufferDescriptor const &duvDesc,
                                               BufferDescriptor const &dvvDesc,
                                               int workGroupSize)
{
  // create stencil kernel
  if (program) {
    glDeleteProgram(program);
  }

  const char *kernelDefine = "#define OPENSUBDIV_GLSL_COMPUTE_KERNEL_EVAL_PATCHES\n";

  program = compileKernel(
      srcDesc, dstDesc, duDesc, dvDesc, duuDesc, duvDesc, dvvDesc, kernelDefine, workGroupSize);
  if (program == 0) {
    return false;
  }

  // cache uniform locations
  uniformSrcOffset = glGetUniformLocation(program, "srcOffset");
  uniformDstOffset = glGetUniformLocation(program, "dstOffset");
  uniformPatchArray = glGetUniformLocation(program, "patchArray");
  uniformDuDesc = glGetUniformLocation(program, "duDesc");
  uniformDvDesc = glGetUniformLocation(program, "dvDesc");
  uniformDuuDesc = glGetUniformLocation(program, "duuDesc");
  uniformDuvDesc = glGetUniformLocation(program, "duvDesc");
  uniformDvvDesc = glGetUniformLocation(program, "dvvDesc");

  return true;
}

}  // namespace blender::opensubdiv
