#include "main/context.h"
#include "main/colormac.h"
#include "main/fbobject.h"
#include "main/macros.h"
#include "main/teximage.h"
#include "main/renderbuffer.h"
#include "swrast/swrast.h"
#include "swrast/s_context.h"
#include "swrast/s_texfetch.h"


/*
 * Render-to-texture code for GL_EXT_framebuffer_object
 */


static void
delete_texture_wrapper(struct gl_context *ctx, struct gl_renderbuffer *rb)
{
   ASSERT(rb->RefCount == 0);
   free(rb);
}


/**
 * This function creates a renderbuffer object which wraps a texture image.
 * The new renderbuffer is plugged into the given attachment point.
 * This allows rendering into the texture as if it were a renderbuffer.
 */
static void
wrap_texture(struct gl_context *ctx, struct gl_renderbuffer_attachment *att)
{
   struct gl_renderbuffer *rb;
   const GLuint name = 0;

   ASSERT(att->Type == GL_TEXTURE);
   ASSERT(att->Renderbuffer == NULL);

   rb = ctx->Driver.NewRenderbuffer(ctx, name);
   if (!rb) {
      _mesa_error(ctx, GL_OUT_OF_MEMORY, "wrap_texture");
      return;
   }

   /* init base gl_renderbuffer fields */
   _mesa_init_renderbuffer(rb, name);
   /* plug in our texture_renderbuffer-specific functions */
   rb->Delete = delete_texture_wrapper;
   rb->AllocStorage = NULL; /* illegal! */

   /* update attachment point */
   _mesa_reference_renderbuffer(&att->Renderbuffer, rb);
}

/**
 * Update the renderbuffer wrapper for rendering to a texture.
 * For example, update the width, height of the RB based on the texture size,
 * update the internal format info, etc.
 */
static void
update_wrapper(struct gl_context *ctx, struct gl_renderbuffer_attachment *att)
{
   struct gl_renderbuffer *rb = att->Renderbuffer;
   struct swrast_renderbuffer *srb = swrast_renderbuffer(rb);
   struct swrast_texture_image *swImage;
   gl_format format;
   GLuint zOffset;

   (void) ctx;

   swImage = swrast_texture_image(_mesa_get_attachment_teximage(att));
   assert(swImage);

   format = swImage->Base.TexFormat;

   if (att->Texture->Target == GL_TEXTURE_1D_ARRAY_EXT) {
      zOffset = 0;
   }
   else {
      zOffset = att->Zoffset;
   }

   rb->Width = swImage->Base.Width;
   rb->Height = swImage->Base.Height;
   rb->InternalFormat = swImage->Base.InternalFormat;
   rb->_BaseFormat = _mesa_get_format_base_format(format);

   /* Want to store linear values, not sRGB */
   rb->Format = _mesa_get_srgb_format_linear(format);
 
   /* Set the gl_renderbuffer::Buffer field so that mapping the buffer
    * succeeds.
     */
   if (att->Texture->Target == GL_TEXTURE_3D ||
       att->Texture->Target == GL_TEXTURE_2D_ARRAY_EXT) {
      srb->Buffer = swImage->Buffer +
         swImage->ImageOffsets[zOffset] * _mesa_get_format_bytes(format);
   }
   else {
      srb->Buffer = swImage->Buffer;
   }
}



/**
 * Called when rendering to a texture image begins, or when changing
 * the dest mipmap level, cube face, etc.
 * This is a fallback routine for software render-to-texture.
 *
 * Called via the glRenderbufferTexture1D/2D/3D() functions
 * and elsewhere (such as glTexImage2D).
 *
 * The image we're rendering into is
 * att->Texture->Image[att->CubeMapFace][att->TextureLevel];
 * It'll never be NULL.
 *
 * \param fb  the framebuffer object the texture is being bound to
 * \param att  the fb attachment point of the texture
 *
 * \sa _mesa_framebuffer_renderbuffer
 */
void
_swrast_render_texture(struct gl_context *ctx,
                       struct gl_framebuffer *fb,
                       struct gl_renderbuffer_attachment *att)
{
   (void) fb;

   if (!att->Renderbuffer) {
      wrap_texture(ctx, att);
   }
   update_wrapper(ctx, att);
}


void
_swrast_finish_render_texture(struct gl_context *ctx,
                              struct gl_renderbuffer_attachment *att)
{
   /* do nothing */
   /* The renderbuffer texture wrapper will get deleted by the
    * normal mechanism for deleting renderbuffers.
    */
   (void) ctx;
   (void) att;
}