/*
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * The Original Code is Copyright (C) 2005 Blender Foundation.
 * All rights reserved.
 */

/** \file
 * \ingroup gpu
 */

#ifndef __GPU_CODEGEN_H__
#define __GPU_CODEGEN_H__

#include "DNA_customdata_types.h"
#include "DNA_listBase.h"
#include "GPU_material.h"
#include "GPU_glew.h"

struct GPUNode;
struct GPUOutput;
struct GPUShader;
struct GPUVertAttrLayers;
struct GSet;
struct ListBase;

/* Pass Generation
 * - Takes a list of nodes and a desired output, and makes a pass. This
 *   will take ownership of the nodes and free them early if unused or
 *   at the end if used.
 */

typedef enum eGPUDataSource {
  GPU_SOURCE_OUTPUT,
  GPU_SOURCE_CONSTANT,
  GPU_SOURCE_UNIFORM,
  GPU_SOURCE_ATTR,
  GPU_SOURCE_BUILTIN,
  GPU_SOURCE_STRUCT,
  GPU_SOURCE_TEX,
} eGPUDataSource;

typedef enum {
  GPU_NODE_LINK_NONE = 0,
  GPU_NODE_LINK_ATTR,
  GPU_NODE_LINK_BUILTIN,
  GPU_NODE_LINK_COLORBAND,
  GPU_NODE_LINK_CONSTANT,
  GPU_NODE_LINK_IMAGE_BLENDER,
  GPU_NODE_LINK_IMAGE_TILEMAP,
  GPU_NODE_LINK_OUTPUT,
  GPU_NODE_LINK_UNIFORM,
} GPUNodeLinkType;

struct GPUNode {
  struct GPUNode *next, *prev;

  const char *name;

  /* Internal flag to mark nodes during pruning */
  bool tag;

  ListBase inputs;
  ListBase outputs;
};

struct GPUNodeLink {
  GPUNodeStack *socket;

  GPUNodeLinkType link_type;
  int users; /* Refcount */

  union {
    /* GPU_NODE_LINK_CONSTANT | GPU_NODE_LINK_UNIFORM */
    float *data;
    /* GPU_NODE_LINK_BUILTIN */
    eGPUBuiltin builtin;
    /* GPU_NODE_LINK_COLORBAND */
    struct GPUTexture **coba;
    /* GPU_NODE_LINK_OUTPUT */
    struct GPUOutput *output;
    /* GPU_NODE_LINK_ATTR */
    struct {
      const char *attr_name;
      CustomDataType attr_type;
    };
    /* GPU_NODE_LINK_IMAGE_BLENDER | GPU_NODE_LINK_IMAGE_TILEMAP */
    struct {
      struct Image *ima;
      struct ImageUser *iuser;
    };
  };
};

typedef struct GPUOutput {
  struct GPUOutput *next, *prev;

  GPUNode *node;
  eGPUType type;     /* data type = length of vector/matrix */
  GPUNodeLink *link; /* output link */
  int id;            /* unique id as created by code generator */
} GPUOutput;

typedef struct GPUInput {
  struct GPUInput *next, *prev;

  GPUNode *node;
  eGPUType type; /* datatype */
  GPUNodeLink *link;
  int id; /* unique id as created by code generator */

  eGPUDataSource source; /* data source */

  int shaderloc;       /* id from opengl */
  char shadername[32]; /* name in shader */

  /* Content based on eGPUDataSource */
  union {
    /* GPU_SOURCE_CONSTANT | GPU_SOURCE_UNIFORM */
    float vec[16]; /* vector data */
    /* GPU_SOURCE_BUILTIN */
    eGPUBuiltin builtin; /* builtin uniform */
    /* GPU_SOURCE_TEX */
    struct {
      struct GPUTexture **coba; /* input texture, only set at runtime */
      struct Image *ima;        /* image */
      struct ImageUser *iuser;  /* image user */
      bool bindtex;             /* input is responsible for binding the texture? */
      int texid;                /* number for multitexture, starting from zero */
      eGPUType textype;         /* texture type (2D, 1D Array ...) */
    };
    /* GPU_SOURCE_ATTR */
    struct {
      /** Attribute name. */
      char attr_name[MAX_CUSTOMDATA_LAYER_NAME];
      /** ID for vertex attributes. */
      int attr_id;
      /** This is the first one that is bound. */
      bool attr_first;
      /** Attribute type. */
      CustomDataType attr_type;
    };
  };
} GPUInput;

struct GPUPass {
  struct GPUPass *next;

  struct GPUShader *shader;
  char *fragmentcode;
  char *geometrycode;
  char *vertexcode;
  char *defines;
  uint refcount; /* Orphaned GPUPasses gets freed by the garbage collector. */
  uint32_t hash; /* Identity hash generated from all GLSL code. */
  struct {
    char *content;
    uint format;
    int len;
  } binary;
  bool compiled; /* Did we already tried to compile the attached GPUShader. */
};

typedef struct GPUPass GPUPass;

GPUPass *GPU_generate_pass(GPUMaterial *material,
                           GPUNodeLink *frag_outlink,
                           struct GPUVertAttrLayers *attrs,
                           ListBase *nodes,
                           int *builtins,
                           const char *vert_code,
                           const char *geom_code,
                           const char *frag_lib,
                           const char *defines);

struct GPUShader *GPU_pass_shader_get(GPUPass *pass);

void GPU_nodes_extract_dynamic_inputs(struct GPUShader *shader, ListBase *inputs, ListBase *nodes);
void GPU_nodes_get_vertex_attrs(ListBase *nodes, struct GPUVertAttrLayers *attrs);
void GPU_nodes_prune(ListBase *nodes, struct GPUNodeLink *outlink);

bool GPU_pass_compile(GPUPass *pass, const char *shname);
void GPU_pass_release(GPUPass *pass);
void GPU_pass_free_nodes(ListBase *nodes);

void GPU_inputs_free(ListBase *inputs);

void gpu_codegen_init(void);
void gpu_codegen_exit(void);

/* Material calls */

const char *GPU_builtin_name(eGPUBuiltin builtin);
void gpu_material_add_node(struct GPUMaterial *material, struct GPUNode *node);
struct GPUTexture **gpu_material_ramp_texture_row_set(GPUMaterial *mat,
                                                      int size,
                                                      float *pixels,
                                                      float *row);

struct GSet *gpu_material_used_libraries(struct GPUMaterial *material);

#endif
