/*
 * Copyright 2018 Collabora Ltd.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * on the rights to use, copy, modify, merge, publish, distribute, sub
 * license, and/or sell copies of the Software, and to permit persons to whom
 * the Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice (including the next
 * paragraph) shall be included in all copies or substantial portions of the
 * Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHOR(S) AND/OR THEIR SUPPLIERS BE LIABLE FOR ANY CLAIM,
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
 * USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#include "zink_context.h"
#include "zink_framebuffer.h"

#include "zink_render_pass.h"
#include "zink_screen.h"
#include "zink_surface.h"

#include "util/u_framebuffer.h"
#include "util/u_memory.h"
#include "util/u_string.h"

void
zink_destroy_framebuffer(struct zink_screen *screen,
                         struct zink_framebuffer *fb)
{
   hash_table_foreach(&fb->objects, he) {
#if defined(_WIN64) || defined(__x86_64__)
      VKSCR(DestroyFramebuffer)(screen->dev, he->data, NULL);
#else
      VkFramebuffer *ptr = he->data;
      VKSCR(DestroyFramebuffer)(screen->dev, *ptr, NULL);
#endif
   }

   ralloc_free(fb);
}

void
zink_init_framebuffer_imageless(struct zink_screen *screen, struct zink_framebuffer *fb, struct zink_render_pass *rp)
{
   VkFramebuffer ret;

   if (fb->rp == rp)
      return;

   uint32_t hash = _mesa_hash_pointer(rp);

   struct hash_entry *he = _mesa_hash_table_search_pre_hashed(&fb->objects, hash, rp);
   if (he) {
#if defined(_WIN64) || defined(__x86_64__)
      ret = (VkFramebuffer)he->data;
#else
      VkFramebuffer *ptr = he->data;
      ret = *ptr;
#endif
      goto out;
   }

   assert(rp->state.num_cbufs + rp->state.have_zsbuf + rp->state.num_cresolves + rp->state.num_zsresolves == fb->state.num_attachments);

   VkFramebufferCreateInfo fci;
   fci.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
   fci.flags = VK_FRAMEBUFFER_CREATE_IMAGELESS_BIT;
   fci.renderPass = rp->render_pass;
   fci.attachmentCount = fb->state.num_attachments;
   fci.pAttachments = NULL;
   fci.width = fb->state.width;
   fci.height = fb->state.height;
   fci.layers = fb->state.layers + 1;

   VkFramebufferAttachmentsCreateInfo attachments;
   attachments.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_ATTACHMENTS_CREATE_INFO;
   attachments.pNext = NULL;
   attachments.attachmentImageInfoCount = fb->state.num_attachments;
   attachments.pAttachmentImageInfos = fb->infos;
   fci.pNext = &attachments;

   if (VKSCR(CreateFramebuffer)(screen->dev, &fci, NULL, &ret) != VK_SUCCESS)
      return;
#if defined(_WIN64) || defined(__x86_64__)
   _mesa_hash_table_insert_pre_hashed(&fb->objects, hash, rp, ret);
#else
   VkFramebuffer *ptr = ralloc(fb, VkFramebuffer);
   if (!ptr) {
      VKSCR(DestroyFramebuffer)(screen->dev, ret, NULL);
      return;
   }
   *ptr = ret;
   _mesa_hash_table_insert_pre_hashed(&fb->objects, hash, rp, ptr);
#endif
out:
   fb->rp = rp;
   fb->fb = ret;
}

static void
populate_attachment_info(VkFramebufferAttachmentImageInfo *att, struct zink_surface_info *info)
{
   att->sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_ATTACHMENT_IMAGE_INFO;
   att->pNext = NULL;
   memcpy(&att->flags, &info->flags, offsetof(struct zink_surface_info, format));
   att->viewFormatCount = 1 + !!info->format[1];
   att->pViewFormats = info->format;
}

static struct zink_framebuffer *
create_framebuffer_imageless(struct zink_context *ctx, struct zink_framebuffer_state *state)
{
   struct zink_screen *screen = zink_screen(ctx->base.screen);
   struct zink_framebuffer *fb = rzalloc(ctx, struct zink_framebuffer);
   if (!fb)
      return NULL;
   pipe_reference_init(&fb->reference, 1);

   if (!_mesa_hash_table_init(&fb->objects, fb, _mesa_hash_pointer, _mesa_key_pointer_equal))
      goto fail;
   memcpy(&fb->state, state, sizeof(struct zink_framebuffer_state));
   for (int i = 0; i < state->num_attachments; i++)
      populate_attachment_info(&fb->infos[i], &fb->state.infos[i]);

   return fb;
fail:
   zink_destroy_framebuffer(screen, fb);
   return NULL;
}

bool
zink_use_dummy_attachments(const struct zink_context *ctx)
{
   return ctx->disable_color_writes && zink_screen(ctx->base.screen)->driver_workarounds.color_write_missing;
}

struct zink_framebuffer *
zink_get_framebuffer_imageless(struct zink_context *ctx)
{
   assert(zink_screen(ctx->base.screen)->info.have_KHR_imageless_framebuffer);

   struct zink_framebuffer_state state;
   state.num_attachments = ctx->fb_state.nr_cbufs;

   const unsigned cresolve_offset = ctx->fb_state.nr_cbufs + !!ctx->fb_state.zsbuf;
   unsigned num_resolves = 0;
   for (int i = 0; i < ctx->fb_state.nr_cbufs; i++) {
      struct pipe_surface *psurf = ctx->fb_state.cbufs[i];
      if (!psurf || zink_use_dummy_attachments(ctx))
         psurf = ctx->dummy_surface[util_logbase2_ceil(ctx->gfx_pipeline_state.rast_samples+1)];
      struct zink_surface *surface = zink_csurface(psurf);
      struct zink_surface *transient = zink_transient_surface(psurf);
      if (transient) {
         memcpy(&state.infos[i], &transient->info, sizeof(transient->info));
         memcpy(&state.infos[cresolve_offset + i], &surface->info, sizeof(surface->info));
         num_resolves++;
      } else {
         memcpy(&state.infos[i], &surface->info, sizeof(surface->info));
      }
   }

   const unsigned zsresolve_offset = cresolve_offset + num_resolves;
   if (ctx->fb_state.zsbuf) {
      struct pipe_surface *psurf = ctx->fb_state.zsbuf;
      struct zink_surface *surface = zink_csurface(psurf);
      struct zink_surface *transient = zink_transient_surface(psurf);
      if (transient) {
         memcpy(&state.infos[state.num_attachments], &transient->info, sizeof(transient->info));
         memcpy(&state.infos[zsresolve_offset], &surface->info, sizeof(surface->info));
         num_resolves++;
      } else {
         memcpy(&state.infos[state.num_attachments], &surface->info, sizeof(surface->info));
      }
      state.num_attachments++;
   }

   /* avoid bitfield explosion */
   assert(state.num_attachments + num_resolves < 16);
   state.num_attachments += num_resolves;
   state.width = MAX2(ctx->fb_state.width, 1);
   state.height = MAX2(ctx->fb_state.height, 1);
   state.layers = MAX2(util_framebuffer_get_num_layers(&ctx->fb_state), 1) - 1;
   state.samples = ctx->fb_state.samples - 1;

   struct zink_framebuffer *fb;
   struct hash_entry *entry = _mesa_hash_table_search(&ctx->framebuffer_cache, &state);
   if (entry)
      return entry->data;

   fb = create_framebuffer_imageless(ctx, &state);
   _mesa_hash_table_insert(&ctx->framebuffer_cache, &fb->state, fb);

   return fb;
}

void
zink_init_framebuffer(struct zink_screen *screen, struct zink_framebuffer *fb, struct zink_render_pass *rp)
{
   VkFramebuffer ret;

   if (fb->rp == rp)
      return;

   uint32_t hash = _mesa_hash_pointer(rp);

   struct hash_entry *he = _mesa_hash_table_search_pre_hashed(&fb->objects, hash, rp);
   if (he) {
#if defined(_WIN64) || defined(__x86_64__)
      ret = (VkFramebuffer)he->data;
#else
      VkFramebuffer *ptr = he->data;
      ret = *ptr;
#endif
      goto out;
   }

   assert(rp->state.num_cbufs + rp->state.have_zsbuf + rp->state.num_cresolves + rp->state.num_zsresolves == fb->state.num_attachments);

   VkFramebufferCreateInfo fci = {0};
   fci.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
   fci.renderPass = rp->render_pass;
   fci.attachmentCount = fb->state.num_attachments;
   fci.pAttachments = fb->state.attachments;
   fci.width = fb->state.width;
   fci.height = fb->state.height;
   fci.layers = fb->state.layers + 1;

   if (VKSCR(CreateFramebuffer)(screen->dev, &fci, NULL, &ret) != VK_SUCCESS)
      return;
#if defined(_WIN64) || defined(__x86_64__)
   _mesa_hash_table_insert_pre_hashed(&fb->objects, hash, rp, ret);
#else
   VkFramebuffer *ptr = ralloc(fb, VkFramebuffer);
   if (!ptr) {
      VKSCR(DestroyFramebuffer)(screen->dev, ret, NULL);
      return;
   }
   *ptr = ret;
   _mesa_hash_table_insert_pre_hashed(&fb->objects, hash, rp, ptr);
#endif
out:
   fb->rp = rp;
   fb->fb = ret;
}

static struct zink_framebuffer *
create_framebuffer(struct zink_context *ctx,
                   struct zink_framebuffer_state *state,
                   struct pipe_surface **attachments)
{
   struct zink_screen *screen = zink_screen(ctx->base.screen);
   struct zink_framebuffer *fb = rzalloc(NULL, struct zink_framebuffer);
   if (!fb)
      return NULL;

   unsigned num_attachments = 0;
   for (int i = 0; i < state->num_attachments; i++) {
      struct zink_surface *surf;
      if (state->attachments[i]) {
         surf = zink_csurface(attachments[i]);
         /* no ref! */
         fb->surfaces[i] = attachments[i];
         num_attachments++;
         util_dynarray_append(&surf->framebuffer_refs, struct zink_framebuffer*, fb);
      } else {
         surf = zink_csurface(ctx->dummy_surface[util_logbase2_ceil(state->samples+1)]);
         state->attachments[i] = surf->image_view;
      }
   }
   pipe_reference_init(&fb->reference, 1 + num_attachments);

   if (!_mesa_hash_table_init(&fb->objects, fb, _mesa_hash_pointer, _mesa_key_pointer_equal))
      goto fail;
   memcpy(&fb->state, state, sizeof(struct zink_framebuffer_state));

   return fb;
fail:
   zink_destroy_framebuffer(screen, fb);
   return NULL;
}

void
debug_describe_zink_framebuffer(char* buf, const struct zink_framebuffer *ptr)
{
   sprintf(buf, "zink_framebuffer");
}

struct zink_framebuffer *
zink_get_framebuffer(struct zink_context *ctx)
{
   struct zink_screen *screen = zink_screen(ctx->base.screen);

   assert(!screen->info.have_KHR_imageless_framebuffer);

   struct pipe_surface *attachments[2 * (PIPE_MAX_COLOR_BUFS + 1)] = {0};
   const unsigned cresolve_offset = ctx->fb_state.nr_cbufs + !!ctx->fb_state.zsbuf;
   unsigned num_resolves = 0;

   struct zink_framebuffer_state state = {0};
   if (!zink_use_dummy_attachments(ctx)) {
      for (int i = 0; i < ctx->fb_state.nr_cbufs; i++) {
         struct pipe_surface *psurf = ctx->fb_state.cbufs[i];
         if (psurf) {
            struct zink_surface *surf = zink_csurface(psurf);
            struct zink_surface *transient = zink_transient_surface(psurf);
            if (transient) {
               state.attachments[i] = transient->image_view;
               state.attachments[cresolve_offset + i] = surf->image_view;
               attachments[cresolve_offset + i] = psurf;
               psurf = &transient->base;
               num_resolves++;
            } else {
               state.attachments[i] = surf->image_view;
            }
         } else {
            state.attachments[i] = VK_NULL_HANDLE;
         }
         attachments[i] = psurf;
      }
   }

   state.num_attachments = ctx->fb_state.nr_cbufs;
   const unsigned zsresolve_offset = cresolve_offset + num_resolves;
   if (ctx->fb_state.zsbuf) {
      struct pipe_surface *psurf = ctx->fb_state.zsbuf;
      if (psurf) {
         struct zink_surface *surf = zink_csurface(psurf);
         struct zink_surface *transient = zink_transient_surface(psurf);
         if (transient) {
            state.attachments[state.num_attachments] = transient->image_view;
            state.attachments[zsresolve_offset] = surf->image_view;
            attachments[zsresolve_offset] = psurf;
            psurf = &transient->base;
            num_resolves++;
         } else {
            state.attachments[state.num_attachments] = surf->image_view;
         }
      } else {
         state.attachments[state.num_attachments] = VK_NULL_HANDLE;
      }
      attachments[state.num_attachments++] = psurf;
   }

   /* avoid bitfield explosion */
   assert(state.num_attachments + num_resolves < 16);
   state.num_attachments += num_resolves;
   state.width = MAX2(ctx->fb_state.width, 1);
   state.height = MAX2(ctx->fb_state.height, 1);
   state.layers = MAX2(util_framebuffer_get_num_layers(&ctx->fb_state), 1) - 1;
   state.samples = ctx->fb_state.samples - 1;

   struct zink_framebuffer *fb;
   simple_mtx_lock(&screen->framebuffer_mtx);
   struct hash_entry *entry = _mesa_hash_table_search(&screen->framebuffer_cache, &state);
   if (entry) {
      fb = (void*)entry->data;
      struct zink_framebuffer *fb_ref = NULL;
      /* this gains 1 ref every time we reuse it */
      zink_framebuffer_reference(screen, &fb_ref, fb);
   } else {
      /* this adds 1 extra ref on creation because all newly-created framebuffers are
       * going to be bound; necessary to handle framebuffers which have no "real" attachments
       * and are only using null surfaces since the only ref they get is the extra one here
       */
      fb = create_framebuffer(ctx, &state, attachments);
      _mesa_hash_table_insert(&screen->framebuffer_cache, &fb->state, fb);
   }
   simple_mtx_unlock(&screen->framebuffer_mtx);

   return fb;
}
