/*
 * dc-yuv2rgb.c
 * by Mike Melanson (mike at multimedia.cx)
 * No license (or warranty). Use this program however you see fit.
 * 
 * This program demonstrates how to use 3D texturing/blending hardware to
 * convert YUV data to RGB as outlined in this paper:
 *
 *  http://www.multimedia.cx/yuv-3d-rgb.txt
 *
 * The program is written for the Sega Dreamcast video game console 
 * with its PowerVR graphics chip. It is designed to be
 * run with a standard KOS development setup available from:
 *
 *  http://cadcdev.sourceforge.net/
 *
 * It was developed with KOS v1.2.0 but probably operates fine under 
 * earlier versions as well.
 *
 * Function: This program simply uses the YUV -> RGB conversion technique
 * to display a 512x256-pixel, quad-color texture on the screen until the
 * start button is pressed on the Dreamcast controller.
 */

#include <stdio.h>
#include <kos.h>

static unsigned short SubVTable[256];

void init_palettes(void) {

  int i;
  unsigned char r, g, b;

  pvr_set_pal_format(PVR_PAL_ARGB8888);

  /* palette #0 (entries 0-255) is the base Y values */
  for (i = 0; i < 256; i++)
    pvr_set_pal_entry(i, 0xFF000000 | (i << 16) | (i << 8) | (i << 0));

  /* palette #1 (entries 256-511) are the additive U values */
  /* red will not be altered by this pass */
  r = 0;
  for (i = 0; i < 256; i++) {
    /* only U values < 128 will affect the green plane on this pass */
    if (i < 128)
      g = (128 - i) * 0.338;
    else
      g = 0;

    /* only U values > 128 will affect the blue plane on this pass */
    if (i > 128)
      b = (i - 128) * 1.732;
    else
      b = 0;

    pvr_set_pal_entry(i + 256, 0xFF000000 | (r << 16) | (g << 8) | (b << 0));
  }

  /* palette #2 (entries 512-767) is the subtractive U values */
  /* red will not be altered by this pass */
  r = 0;
  for (i = 0; i < 256; i++) {
    /* only U values > 128 will affect the green plane on this pass */
    if (i > 128)
      g = (i - 128) * 0.338;
    else
      g = 0;

    /* only U values < 128 will affect the blue plane on this pass */
    if (i < 128)
      b = (128 - i) * 1.732;
    else
      b = 0;

    pvr_set_pal_entry(i + 512, 0xFF000000 | (r << 16) | (g << 8) | (b << 0));
  }

  /* palette #3 (entries 768-1023) is the additive V values */
  /* blue will not be altered by this pass */
  b = 0;
  for (i = 0; i < 256; i++) {
    /* only V values < 128 will affect the green plane on this pass */
    if (i < 128)
      g = (128 - i) * 0.698;
    else
      g = 0;

    /* only V values > 128 will affect the blue plane on this pass */
    if (i > 128)
      r = (i - 128) * 1.370;
    else
      r = 0;

    pvr_set_pal_entry(i + 768, 0xFF000000 | (r << 16) | (g << 8) | (b << 0));
  }

  /* palette #4 (not an official palette) is the subtractive V values */
  /* blue will not be altered by this pass */
  b = 0;
  for (i = 0; i < 256; i++) {
    /* only V values > 128 will affect the green plane on this pass */
    if (i > 128)
      g = (i - 128) * 0.698;
    else
      g = 0;

    /* only V values < 128 will affect the blue plane on this pass */
    if (i < 128)
      r = (128 - i) * 1.370;
    else
      r = 0;

    /* top 5 bits of r, top 6 bits of green, b will always be 0 */
    SubVTable[i] = ((r >> 3) << 11) | ((g >> 2) << 5);
  }
}

void submit_tr_poly(int x1, int y1, int x2, int y2, int width, int height, 
  int palette, pvr_ptr_t texture_ptr, int texture_format, int poly_type,
  int pass_type) {

  pvr_poly_cxt_t cxt;
  pvr_poly_hdr_t hdr;
  pvr_vertex_t vert;

  pvr_poly_cxt_txr(&cxt, poly_type, texture_format, width, height,
    texture_ptr, PVR_FILTER_BILINEAR);
  cxt.txr.format |= PVR_TXRFMT_8BPP_PAL(palette);
  if (poly_type != PVR_LIST_OP_POLY) {
    if (pass_type == 0) {
      cxt.blend.src = PVR_BLEND_ONE;
      cxt.blend.dst = PVR_BLEND_ONE;
    } else {
      cxt.blend.src = PVR_BLEND_INVDESTCOLOR;
      cxt.blend.dst = PVR_BLEND_INVDESTCOLOR;
    }
  }
  pvr_poly_compile(&hdr, &cxt);
  pvr_prim(&hdr, sizeof(hdr));

  vert.argb = PVR_PACK_COLOR(1.0f, 1.0f, 1.0f, 1.0f);
  vert.oargb = 0;
  vert.flags = PVR_CMD_VERTEX;

  vert.x = x1;
  vert.y = y1;
  vert.z = 1;
  vert.u = 0.0;
  vert.v = 0.0;
  pvr_prim(&vert, sizeof(vert));

  vert.x = x2 - 1;
  vert.y = y1;
  vert.z = 1;
  vert.u = 1.0;
  vert.v = 0.0;
  pvr_prim(&vert, sizeof(vert));

  vert.x = x1;
  vert.y = y2 - 1;
  vert.z = 1;
  vert.u = 0.0;
  vert.v = 1.0;
  pvr_prim(&vert, sizeof(vert));

  vert.x = x2 - 1;
  vert.y = y2 - 1;
  vert.z = 1;
  vert.u = 1.0;
  vert.v = 1.0;
  vert.flags = PVR_CMD_VERTEX_EOL;
  pvr_prim(&vert, sizeof(vert));
}

#define WIDTH 512
#define HEIGHT 256

int main() {

  unsigned char y_plane[WIDTH * HEIGHT];
  unsigned char u_plane[WIDTH * HEIGHT / 4];
  unsigned char v_plane[WIDTH * HEIGHT / 4];
  unsigned short sub_v_plane[WIDTH * HEIGHT / 4];

  pvr_ptr_t y_texture;
  pvr_ptr_t u_texture;
  pvr_ptr_t v_texture;
  pvr_ptr_t sub_v_texture;

  int i, x, y;
  cont_cond_t cont;

  vid_set_mode(DM_640x480_NTSC_IL, PM_RGB565);

  /* init pvr subsystem */
  pvr_init_defaults();

  /* fill up the palettes */
  init_palettes();

  /* allocate the textures */
  y_texture = pvr_mem_malloc(WIDTH * HEIGHT);
  u_texture = pvr_mem_malloc(WIDTH * HEIGHT / 4);
  v_texture = pvr_mem_malloc(WIDTH * HEIGHT / 4);
  sub_v_texture = pvr_mem_malloc(WIDTH * HEIGHT / 2);

  if (!y_texture || !u_texture || !v_texture || !sub_v_texture) {
    printf ("*** could not allocate textures\n");
    return 0;
  }

  /* contrive a quad-colored YUV texture:
   *   white   red
   *   green   blue 
   *
   * white = (255, 128, 128)
   * red = (65, 90, 240)
   * green = (129, 91, 24)
   * blue = (25, 240, 110)
   */

  for (y = 0; y < HEIGHT / 2; y++) {
    for (x = 0; x < WIDTH / 2; x++) {
      y_plane[y * WIDTH + x] = 0xFF;
      y_plane[y * WIDTH + (WIDTH / 2) + x] = 65;
      y_plane[(y + (HEIGHT / 2)) * WIDTH + x] = 129;
      y_plane[(y + (HEIGHT / 2)) * WIDTH + (WIDTH / 2) + x] = 25;

    }
  }

  for (y = 0; y < HEIGHT / 4; y++) {
    for (x = 0; x < WIDTH / 4; x++) {
      u_plane[y * (WIDTH / 2) + x] = 0x80;
      u_plane[y * (WIDTH / 2) + (WIDTH / 4) + x] = 90;
      u_plane[(y + (HEIGHT / 4)) * (WIDTH / 2) + x] = 91;
      u_plane[(y + (HEIGHT / 4)) * (WIDTH / 2) + (WIDTH / 4) + x] = 240;

      v_plane[y * (WIDTH / 2) + x] = 0x80;
      v_plane[y * (WIDTH / 2) + (WIDTH / 4) + x] = 240;
      v_plane[(y + (HEIGHT / 4)) * (WIDTH / 2) + x] = 24;
      v_plane[(y + (HEIGHT / 4)) * (WIDTH / 2) + (WIDTH / 4) + x] = 110;
    }
  }

  /* build the subtractive V plane */
  for (i = 0; i < (WIDTH * HEIGHT) / 4; i++) {
    sub_v_plane[i] = SubVTable[v_plane[i]];
  }

  /* twiddle and transfer the planes into texture memory */
  pvr_txr_load_ex(y_plane, y_texture, WIDTH, HEIGHT, PVR_TXRLOAD_8BPP);
  pvr_txr_load_ex(u_plane, u_texture, WIDTH / 2, HEIGHT / 2, PVR_TXRLOAD_8BPP);
  pvr_txr_load_ex(v_plane, v_texture, WIDTH / 2, HEIGHT / 2, PVR_TXRLOAD_8BPP);
  pvr_txr_load_ex(sub_v_plane, sub_v_texture, WIDTH / 2, HEIGHT / 2, PVR_TXRLOAD_16BPP);

/* do the sequence twice because of the double-buffering */
for (i = 0; i < 2; i++) {

  /* plot the textures and do the math */

  /* prep the PVR hardware */
  pvr_wait_ready();
  pvr_scene_begin();

  /* base Y plane */
  pvr_list_begin(PVR_LIST_OP_POLY);
  submit_tr_poly(0, 0, WIDTH, HEIGHT, WIDTH, HEIGHT, 0, y_texture, 
    PVR_TXRFMT_PAL8BPP, PVR_LIST_OP_POLY, 0);
  pvr_list_finish();

  pvr_list_begin(PVR_LIST_TR_POLY);

  /* additive U pass */
  submit_tr_poly(0, 0, WIDTH, HEIGHT, WIDTH / 2, HEIGHT / 2, 1, u_texture, 
    PVR_TXRFMT_PAL8BPP, PVR_LIST_TR_POLY, 0);

  /* subtractive U pass */
  submit_tr_poly(0, 0, WIDTH, HEIGHT, WIDTH / 2, HEIGHT / 2, 2, u_texture, 
    PVR_TXRFMT_PAL8BPP, PVR_LIST_TR_POLY, 1);

  /* additive V pass */
  submit_tr_poly(0, 0, WIDTH, HEIGHT, WIDTH / 2, HEIGHT / 2, 3, v_texture, 
    PVR_TXRFMT_PAL8BPP, PVR_LIST_TR_POLY, 0);

  /* subtractive V pass with 16-bit texture */
  submit_tr_poly(0, 0, WIDTH, HEIGHT, WIDTH / 2, HEIGHT / 2, 0, sub_v_texture, 
    PVR_TXRFMT_RGB565, PVR_LIST_TR_POLY, 1);

  pvr_list_finish();
  pvr_scene_finish();
}

  printf ("  press start to exit...\n");
  do {
    if (cont_get_cond(maple_first_controller(), &cont))
      printf ("Error getting controller status\n");
    cont.buttons = ~cont.buttons;
    vid_waitvbl();
  } while (!(cont.buttons & CONT_START));

  return 0;
}

