#include "zink_clear.h"
#include "zink_context.h"
#include "zink_format.h"
#include "zink_inlines.h"
#include "zink_kopper.h"
#include "zink_helpers.h"
#include "zink_query.h"
#include "zink_resource.h"
#include "zink_screen.h"

#include "util/u_blitter.h"
#include "util/u_rect.h"
#include "util/u_surface.h"
#include "util/format/u_format.h"

static void
apply_dst_clears(struct zink_context *ctx, const struct pipe_blit_info *info, bool discard_only)
{
   if (info->scissor_enable) {
      struct u_rect rect = { info->scissor.minx, info->scissor.maxx,
                             info->scissor.miny, info->scissor.maxy };
      zink_fb_clears_apply_or_discard(ctx, info->dst.resource, rect, discard_only);
   } else
      zink_fb_clears_apply_or_discard(ctx, info->dst.resource, zink_rect_from_box(&info->dst.box), discard_only);
}

static bool
blit_resolve(struct zink_context *ctx, const struct pipe_blit_info *info, bool *needs_present_readback)
{
   if (util_format_get_mask(info->dst.format) != info->mask ||
       util_format_get_mask(info->src.format) != info->mask ||
       util_format_is_depth_or_stencil(info->dst.format) ||
       info->scissor_enable ||
       info->alpha_blend)
      return false;

   if (info->src.box.width < 0 ||
       info->dst.box.width < 0 ||
       info->src.box.height < 0 ||
       info->dst.box.height < 0 ||
       info->src.box.depth < 0 ||
       info->dst.box.depth < 0)
      return false;
   /* vulkan resolves can't downscale */
   if (info->src.box.width > info->dst.box.width ||
       info->src.box.height > info->dst.box.height ||
       info->src.box.depth > info->dst.box.depth)
      return false;

   if (info->render_condition_enable &&
       ctx->render_condition_active)
      return false;

   struct zink_resource *src = zink_resource(info->src.resource);
   struct zink_resource *use_src = src;
   struct zink_resource *dst = zink_resource(info->dst.resource);

   struct zink_screen *screen = zink_screen(ctx->base.screen);
   /* aliased/swizzled formats need u_blitter */
   if (src->format != zink_get_format(screen, info->src.format) ||
       dst->format != zink_get_format(screen, info->dst.format))
      return false;
   if (src->format != dst->format)
      return false;


   apply_dst_clears(ctx, info, false);
   zink_fb_clears_apply_region(ctx, info->src.resource, zink_rect_from_box(&info->src.box));

   if (src->obj->dt)
      *needs_present_readback = zink_kopper_acquire_readback(ctx, src, &use_src);

   zink_resource_setup_transfer_layouts(ctx, use_src, dst);
   VkCommandBuffer cmdbuf = *needs_present_readback ?
                            ctx->bs->cmdbuf :
                            zink_get_cmdbuf(ctx, src, dst);
   zink_batch_reference_resource_rw(ctx, use_src, false);
   zink_batch_reference_resource_rw(ctx, dst, true);

   bool marker = zink_cmd_debug_marker_begin(ctx, cmdbuf, "blit_resolve(%s->%s, %dx%d->%dx%d)",
                                             util_format_short_name(info->src.format),
                                             util_format_short_name(info->src.format),
                                             info->src.box.width, info->src.box.height,
                                             info->dst.box.width, info->dst.box.height);
   VkImageResolve region = {0};

   region.srcSubresource.aspectMask = src->aspect;
   region.srcSubresource.mipLevel = info->src.level;
   region.srcOffset.x = info->src.box.x;
   region.srcOffset.y = info->src.box.y;

   if (src->base.b.array_size > 1) {
      region.srcOffset.z = 0;
      region.srcSubresource.baseArrayLayer = info->src.box.z;
      region.srcSubresource.layerCount = info->src.box.depth;
   } else {
      assert(info->src.box.depth == 1);
      region.srcOffset.z = info->src.box.z;
      region.srcSubresource.baseArrayLayer = 0;
      region.srcSubresource.layerCount = 1;
   }

   region.dstSubresource.aspectMask = dst->aspect;
   region.dstSubresource.mipLevel = info->dst.level;
   region.dstOffset.x = info->dst.box.x;
   region.dstOffset.y = info->dst.box.y;

   if (dst->base.b.array_size > 1) {
      region.dstOffset.z = 0;
      region.dstSubresource.baseArrayLayer = info->dst.box.z;
      region.dstSubresource.layerCount = info->dst.box.depth;
   } else {
      assert(info->dst.box.depth == 1);
      region.dstOffset.z = info->dst.box.z;
      region.dstSubresource.baseArrayLayer = 0;
      region.dstSubresource.layerCount = 1;
   }

   region.extent.width = info->dst.box.width;
   region.extent.height = info->dst.box.height;
   region.extent.depth = info->dst.box.depth;
   if (region.srcOffset.x + region.extent.width >= u_minify(src->base.b.width0, region.srcSubresource.mipLevel))
      region.extent.width = u_minify(src->base.b.width0, region.srcSubresource.mipLevel) - region.srcOffset.x;
   if (region.dstOffset.x + region.extent.width >= u_minify(dst->base.b.width0, region.dstSubresource.mipLevel))
      region.extent.width = u_minify(dst->base.b.width0, region.dstSubresource.mipLevel) - region.dstOffset.x;
   if (region.srcOffset.y + region.extent.height >= u_minify(src->base.b.height0, region.srcSubresource.mipLevel))
      region.extent.height = u_minify(src->base.b.height0, region.srcSubresource.mipLevel) - region.srcOffset.y;
   if (region.dstOffset.y + region.extent.height >= u_minify(dst->base.b.height0, region.dstSubresource.mipLevel))
      region.extent.height = u_minify(dst->base.b.height0, region.dstSubresource.mipLevel) - region.dstOffset.y;
   if (region.srcOffset.z + region.extent.depth >= u_minify(src->base.b.depth0, region.srcSubresource.mipLevel))
      region.extent.depth = u_minify(src->base.b.depth0, region.srcSubresource.mipLevel) - region.srcOffset.z;
   if (region.dstOffset.z + region.extent.depth >= u_minify(dst->base.b.depth0, region.dstSubresource.mipLevel))
      region.extent.depth = u_minify(dst->base.b.depth0, region.dstSubresource.mipLevel) - region.dstOffset.z;
   VKCTX(CmdResolveImage)(cmdbuf, use_src->obj->image, src->layout,
                     dst->obj->image, dst->layout,
                     1, &region);
   zink_cmd_debug_marker_end(ctx, cmdbuf, marker);

   return true;
}

static bool
blit_native(struct zink_context *ctx, const struct pipe_blit_info *info, bool *needs_present_readback)
{
   if (util_format_get_mask(info->dst.format) != info->mask ||
       util_format_get_mask(info->src.format) != info->mask ||
       info->scissor_enable ||
       info->alpha_blend)
      return false;

   if (info->render_condition_enable &&
       ctx->render_condition_active)
      return false;

   if (util_format_is_depth_or_stencil(info->dst.format) &&
       (info->dst.format != info->src.format || info->filter == PIPE_TEX_FILTER_LINEAR))
      return false;

   /* vkCmdBlitImage must not be used for multisampled source or destination images. */
   if (info->src.resource->nr_samples > 1 || info->dst.resource->nr_samples > 1)
      return false;

   struct zink_resource *src = zink_resource(info->src.resource);
   struct zink_resource *use_src = src;
   struct zink_resource *dst = zink_resource(info->dst.resource);

   struct zink_screen *screen = zink_screen(ctx->base.screen);
   if (src->format != zink_get_format(screen, info->src.format) ||
       dst->format != zink_get_format(screen, info->dst.format))
      return false;
   if (src->format != VK_FORMAT_A8_UNORM_KHR && zink_format_is_emulated_alpha(info->src.format))
      return false;

   if (!(src->obj->vkfeats & VK_FORMAT_FEATURE_BLIT_SRC_BIT) ||
       !(dst->obj->vkfeats & VK_FORMAT_FEATURE_BLIT_DST_BIT))
      return false;

   if ((util_format_is_pure_sint(info->src.format) !=
        util_format_is_pure_sint(info->dst.format)) ||
       (util_format_is_pure_uint(info->src.format) !=
        util_format_is_pure_uint(info->dst.format)))
      return false;

   if (info->filter == PIPE_TEX_FILTER_LINEAR &&
       !(src->obj->vkfeats & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT))
      return false;


   VkImageBlit region = {0};
   region.srcSubresource.aspectMask = src->aspect;
   region.srcSubresource.mipLevel = info->src.level;
   region.srcOffsets[0].x = info->src.box.x;
   region.srcOffsets[0].y = info->src.box.y;
   region.srcOffsets[1].x = info->src.box.x + info->src.box.width;
   region.srcOffsets[1].y = info->src.box.y + info->src.box.height;

   enum pipe_texture_target src_target = src->base.b.target;
   if (src->need_2D)
      src_target = src_target == PIPE_TEXTURE_1D ? PIPE_TEXTURE_2D : PIPE_TEXTURE_2D_ARRAY;
   switch (src_target) {
   case PIPE_TEXTURE_CUBE:
   case PIPE_TEXTURE_CUBE_ARRAY:
   case PIPE_TEXTURE_2D_ARRAY:
   case PIPE_TEXTURE_1D_ARRAY:
      /* these use layer */
      region.srcSubresource.baseArrayLayer = info->src.box.z;
      /* VUID-vkCmdBlitImage-srcImage-00240 */
      if (region.srcSubresource.baseArrayLayer && dst->base.b.target == PIPE_TEXTURE_3D)
         return false;
      region.srcSubresource.layerCount = info->src.box.depth;
      region.srcOffsets[0].z = 0;
      region.srcOffsets[1].z = 1;
      break;
   case PIPE_TEXTURE_3D:
      /* this uses depth */
      region.srcSubresource.baseArrayLayer = 0;
      region.srcSubresource.layerCount = 1;
      region.srcOffsets[0].z = info->src.box.z;
      region.srcOffsets[1].z = info->src.box.z + info->src.box.depth;
      break;
   default:
      /* these must only copy one layer */
      region.srcSubresource.baseArrayLayer = 0;
      region.srcSubresource.layerCount = 1;
      region.srcOffsets[0].z = 0;
      region.srcOffsets[1].z = 1;
   }

   region.dstSubresource.aspectMask = dst->aspect;
   region.dstSubresource.mipLevel = info->dst.level;
   region.dstOffsets[0].x = info->dst.box.x;
   region.dstOffsets[0].y = info->dst.box.y;
   region.dstOffsets[1].x = info->dst.box.x + info->dst.box.width;
   region.dstOffsets[1].y = info->dst.box.y + info->dst.box.height;
   assert(region.dstOffsets[0].x != region.dstOffsets[1].x);
   assert(region.dstOffsets[0].y != region.dstOffsets[1].y);

   enum pipe_texture_target dst_target = dst->base.b.target;
   if (dst->need_2D)
      dst_target = dst_target == PIPE_TEXTURE_1D ? PIPE_TEXTURE_2D : PIPE_TEXTURE_2D_ARRAY;
   switch (dst_target) {
   case PIPE_TEXTURE_CUBE:
   case PIPE_TEXTURE_CUBE_ARRAY:
   case PIPE_TEXTURE_2D_ARRAY:
   case PIPE_TEXTURE_1D_ARRAY:
      /* these use layer */
      region.dstSubresource.baseArrayLayer = info->dst.box.z;
      /* VUID-vkCmdBlitImage-srcImage-00240 */
      if (region.dstSubresource.baseArrayLayer && src->base.b.target == PIPE_TEXTURE_3D)
         return false;
      region.dstSubresource.layerCount = info->dst.box.depth;
      region.dstOffsets[0].z = 0;
      region.dstOffsets[1].z = 1;
      break;
   case PIPE_TEXTURE_3D:
      /* this uses depth */
      region.dstSubresource.baseArrayLayer = 0;
      region.dstSubresource.layerCount = 1;
      region.dstOffsets[0].z = info->dst.box.z;
      region.dstOffsets[1].z = info->dst.box.z + info->dst.box.depth;
      break;
   default:
      /* these must only copy one layer */
      region.dstSubresource.baseArrayLayer = 0;
      region.dstSubresource.layerCount = 1;
      region.dstOffsets[0].z = 0;
      region.dstOffsets[1].z = 1;
   }
   assert(region.dstOffsets[0].z != region.dstOffsets[1].z);

   apply_dst_clears(ctx, info, false);
   zink_fb_clears_apply_region(ctx, info->src.resource, zink_rect_from_box(&info->src.box));

   if (src->obj->dt)
      *needs_present_readback = zink_kopper_acquire_readback(ctx, src, &use_src);

   zink_resource_setup_transfer_layouts(ctx, use_src, dst);
   VkCommandBuffer cmdbuf = *needs_present_readback ?
                            ctx->bs->cmdbuf :
                            zink_get_cmdbuf(ctx, src, dst);
   zink_batch_reference_resource_rw(ctx, use_src, false);
   zink_batch_reference_resource_rw(ctx, dst, true);

   bool marker = zink_cmd_debug_marker_begin(ctx, cmdbuf, "blit_native(%s->%s, %dx%d->%dx%d)",
                                             util_format_short_name(info->src.format),
                                             util_format_short_name(info->src.format),
                                             info->src.box.width, info->src.box.height,
                                             info->dst.box.width, info->dst.box.height);

   VKCTX(CmdBlitImage)(cmdbuf, use_src->obj->image, src->layout,
                  dst->obj->image, dst->layout,
                  1, &region,
                  zink_filter(info->filter));

   zink_cmd_debug_marker_end(ctx, cmdbuf, marker);

   return true;
}

static bool
try_copy_region(struct pipe_context *pctx, const struct pipe_blit_info *info)
{
   struct zink_context *ctx = zink_context(pctx);
   struct zink_resource *src = zink_resource(info->src.resource);
   struct zink_resource *dst = zink_resource(info->dst.resource);
   /* if we're copying between resources with matching aspects then we can probably just copy_region */
   if (src->aspect != dst->aspect)
      return false;
   struct pipe_blit_info new_info = *info;

   if (src->aspect & VK_IMAGE_ASPECT_STENCIL_BIT &&
       new_info.render_condition_enable &&
       !ctx->render_condition_active)
      new_info.render_condition_enable = false;

   return util_try_blit_via_copy_region(pctx, &new_info, ctx->render_condition_active);
}

void
zink_blit(struct pipe_context *pctx,
          const struct pipe_blit_info *info)
{
   struct zink_context *ctx = zink_context(pctx);
   const struct util_format_description *src_desc = util_format_description(info->src.format);
   const struct util_format_description *dst_desc = util_format_description(info->dst.format);

   struct zink_resource *src = zink_resource(info->src.resource);
   struct zink_resource *use_src = src;
   struct zink_resource *dst = zink_resource(info->dst.resource);
   bool needs_present_readback = false;
   if (zink_is_swapchain(dst)) {
      if (!zink_kopper_acquire(ctx, dst, UINT64_MAX))
         return;
   }

   if (src_desc == dst_desc ||
       src_desc->nr_channels != 4 || src_desc->layout != UTIL_FORMAT_LAYOUT_PLAIN ||
       (src_desc->nr_channels == 4 && src_desc->channel[3].type != UTIL_FORMAT_TYPE_VOID)) {
      /* we can't blit RGBX -> RGBA formats directly since they're emulated
       * so we have to use sampler views
       */
      if (info->src.resource->nr_samples > 1 &&
          info->dst.resource->nr_samples <= 1) {
         if (blit_resolve(ctx, info, &needs_present_readback))
            goto end;
      } else {
         if (try_copy_region(pctx, info))
            goto end;
         if (blit_native(ctx, info, &needs_present_readback))
            goto end;
      }
   }



   bool stencil_blit = false;
   if (!util_blitter_is_blit_supported(ctx->blitter, info)) {
      if (util_format_is_depth_or_stencil(info->src.resource->format)) {
         if (info->mask & PIPE_MASK_Z) {
            struct pipe_blit_info depth_blit = *info;
            depth_blit.mask = PIPE_MASK_Z;
            if (util_blitter_is_blit_supported(ctx->blitter, &depth_blit)) {
               zink_blit_begin(ctx, ZINK_BLIT_SAVE_FB | ZINK_BLIT_SAVE_FS | ZINK_BLIT_SAVE_TEXTURES);
               util_blitter_blit(ctx->blitter, &depth_blit, NULL);
            } else {
               mesa_loge("ZINK: depth blit unsupported %s -> %s",
                         util_format_short_name(info->src.resource->format),
                         util_format_short_name(info->dst.resource->format));
            }
         }
         if (info->mask & PIPE_MASK_S)
            stencil_blit = true;
      }
      if (!stencil_blit) {
         mesa_loge("ZINK: blit unsupported %s -> %s",
                 util_format_short_name(info->src.resource->format),
                 util_format_short_name(info->dst.resource->format));
         goto end;
      }
   }

   if (src->obj->dt) {
      zink_fb_clears_apply_region(ctx, info->src.resource, zink_rect_from_box(&info->src.box));
      needs_present_readback = zink_kopper_acquire_readback(ctx, src, &use_src);
   }

   /* this is discard_only because we're about to start a renderpass that will
    * flush all pending clears anyway
    */
   apply_dst_clears(ctx, info, true);
   zink_fb_clears_apply_region(ctx, info->src.resource, zink_rect_from_box(&info->src.box));
   unsigned rp_clears_enabled = ctx->rp_clears_enabled;
   unsigned clears_enabled = ctx->clears_enabled;
   if (!dst->fb_bind_count) {
      /* avoid applying clears from fb unbind by storing and re-setting them after the blit */
      ctx->rp_clears_enabled = 0;
      ctx->clears_enabled = 0;
   } else {
      unsigned bit;
      /* convert to PIPE_CLEAR_XYZ */
      if (dst->fb_binds & BITFIELD_BIT(PIPE_MAX_COLOR_BUFS))
         bit = PIPE_CLEAR_DEPTHSTENCIL;
      else
         bit = dst->fb_binds << 2;
      rp_clears_enabled &= ~bit;
      clears_enabled &= ~bit;
      ctx->rp_clears_enabled &= bit;
      ctx->clears_enabled &= bit;
   }

   /* this will draw a full-resource quad, so ignore existing data */
   bool whole = util_blit_covers_whole_resource(info);
   if (whole)
      pctx->invalidate_resource(pctx, info->dst.resource);

   ctx->unordered_blitting = !(info->render_condition_enable && ctx->render_condition_active) &&
                             zink_screen(ctx->base.screen)->info.have_KHR_dynamic_rendering &&
                             !needs_present_readback &&
                             zink_get_cmdbuf(ctx, src, dst) == ctx->bs->reordered_cmdbuf;
   VkCommandBuffer cmdbuf = ctx->bs->cmdbuf;
   VkPipeline pipeline = ctx->gfx_pipeline_state.pipeline;
   bool in_rp = ctx->in_rp;
   uint64_t tc_data = ctx->dynamic_fb.tc_info.data;
   bool queries_disabled = ctx->queries_disabled;
   bool rp_changed = ctx->rp_changed || (!ctx->fb_state.zsbuf && util_format_is_depth_or_stencil(info->dst.format));
   unsigned ds3_states = ctx->ds3_states;
   bool rp_tc_info_updated = ctx->rp_tc_info_updated;
   if (ctx->unordered_blitting) {
      /* for unordered blit, swap the unordered cmdbuf for the main one for the whole op to avoid conditional hell */
      ctx->bs->cmdbuf = ctx->bs->reordered_cmdbuf;
      ctx->in_rp = false;
      ctx->rp_changed = true;
      ctx->queries_disabled = true;
      ctx->pipeline_changed[0] = true;
      zink_reset_ds3_states(ctx);
      zink_select_draw_vbo(ctx);
   }
   zink_blit_begin(ctx, ZINK_BLIT_SAVE_FB | ZINK_BLIT_SAVE_FS | ZINK_BLIT_SAVE_TEXTURES);
   if (zink_format_needs_mutable(info->src.format, info->src.resource->format))
      zink_resource_object_init_mutable(ctx, src);
   if (zink_format_needs_mutable(info->dst.format, info->dst.resource->format))
      zink_resource_object_init_mutable(ctx, dst);
   zink_blit_barriers(ctx, use_src, dst, whole);
   ctx->blitting = true;
   ctx->blit_scissor = info->scissor_enable;
   ctx->blit_nearest = info->filter == PIPE_TEX_FILTER_NEAREST;

   if (stencil_blit) {
      struct pipe_surface *dst_view, dst_templ;
      util_blitter_default_dst_texture(&dst_templ, info->dst.resource, info->dst.level, info->dst.box.z);
      dst_view = pctx->create_surface(pctx, info->dst.resource, &dst_templ);

      util_blitter_clear_depth_stencil(ctx->blitter, dst_view, PIPE_CLEAR_STENCIL,
                                       0, 0, info->dst.box.x, info->dst.box.y,
                                       info->dst.box.width, info->dst.box.height);
      zink_blit_begin(ctx, ZINK_BLIT_SAVE_FB | ZINK_BLIT_SAVE_FS | ZINK_BLIT_SAVE_TEXTURES | ZINK_BLIT_SAVE_FS_CONST_BUF);
      util_blitter_stencil_fallback(ctx->blitter,
                                    info->dst.resource,
                                    info->dst.level,
                                    &info->dst.box,
                                    info->src.resource,
                                    info->src.level,
                                    &info->src.box,
                                    info->scissor_enable ? &info->scissor : NULL);

      pipe_surface_release(pctx, &dst_view);
   } else {
      struct pipe_blit_info new_info = *info;
      new_info.src.resource = &use_src->base.b;
      util_blitter_blit(ctx->blitter, &new_info, NULL);
   }
   ctx->blitting = false;
   ctx->rp_clears_enabled = rp_clears_enabled;
   ctx->clears_enabled = clears_enabled;
   if (ctx->unordered_blitting) {
      zink_batch_no_rp(ctx);
      ctx->in_rp = in_rp;
      ctx->gfx_pipeline_state.rp_state = zink_update_rendering_info(ctx);
      ctx->rp_changed = rp_changed;
      ctx->rp_tc_info_updated |= rp_tc_info_updated;
      ctx->queries_disabled = queries_disabled;
      ctx->dynamic_fb.tc_info.data = tc_data;
      ctx->bs->cmdbuf = cmdbuf;
      ctx->gfx_pipeline_state.pipeline = pipeline;
      ctx->pipeline_changed[0] = true;
      ctx->ds3_states = ds3_states;
      zink_select_draw_vbo(ctx);
   }
   ctx->unordered_blitting = false;
end:
   if (needs_present_readback) {
      src->obj->unordered_read = false;
      dst->obj->unordered_write = false;
      zink_kopper_present_readback(ctx, src);
   }
}

/* similar to radeonsi */
void
zink_blit_begin(struct zink_context *ctx, enum zink_blit_flags flags)
{
   util_blitter_save_vertex_elements(ctx->blitter, ctx->element_state);
   util_blitter_save_viewport(ctx->blitter, ctx->vp_state.viewport_states);

   util_blitter_save_vertex_buffers(ctx->blitter, ctx->vertex_buffers,
                                    util_last_bit(ctx->gfx_pipeline_state.vertex_buffers_enabled_mask));
   util_blitter_save_vertex_shader(ctx->blitter, ctx->gfx_stages[MESA_SHADER_VERTEX]);
   util_blitter_save_tessctrl_shader(ctx->blitter, ctx->gfx_stages[MESA_SHADER_TESS_CTRL]);
   util_blitter_save_tesseval_shader(ctx->blitter, ctx->gfx_stages[MESA_SHADER_TESS_EVAL]);
   util_blitter_save_geometry_shader(ctx->blitter, ctx->gfx_stages[MESA_SHADER_GEOMETRY]);
   util_blitter_save_rasterizer(ctx->blitter, ctx->rast_state);
   util_blitter_save_so_targets(ctx->blitter, ctx->num_so_targets, ctx->so_targets);

   if (flags & ZINK_BLIT_SAVE_FS_CONST_BUF)
      util_blitter_save_fragment_constant_buffer_slot(ctx->blitter, ctx->ubos[MESA_SHADER_FRAGMENT]);

   if (flags & ZINK_BLIT_SAVE_FS) {
      util_blitter_save_blend(ctx->blitter, ctx->gfx_pipeline_state.blend_state);
      util_blitter_save_depth_stencil_alpha(ctx->blitter, ctx->dsa_state);
      util_blitter_save_stencil_ref(ctx->blitter, &ctx->stencil_ref);
      util_blitter_save_sample_mask(ctx->blitter, ctx->gfx_pipeline_state.sample_mask, ctx->gfx_pipeline_state.min_samples + 1);
      util_blitter_save_scissor(ctx->blitter, ctx->vp_state.scissor_states);
      /* also util_blitter_save_window_rectangles when we have that? */

      util_blitter_save_fragment_shader(ctx->blitter, ctx->gfx_stages[MESA_SHADER_FRAGMENT]);
   }

   if (flags & ZINK_BLIT_SAVE_FB)
      util_blitter_save_framebuffer(ctx->blitter, &ctx->fb_state);


   if (flags & ZINK_BLIT_SAVE_TEXTURES) {
      util_blitter_save_fragment_sampler_states(ctx->blitter,
                                                ctx->di.num_samplers[MESA_SHADER_FRAGMENT],
                                                (void**)ctx->sampler_states[MESA_SHADER_FRAGMENT]);
      util_blitter_save_fragment_sampler_views(ctx->blitter,
                                               ctx->di.num_sampler_views[MESA_SHADER_FRAGMENT],
                                               ctx->sampler_views[MESA_SHADER_FRAGMENT]);
   }

   if (flags & ZINK_BLIT_NO_COND_RENDER && ctx->render_condition_active)
      zink_stop_conditional_render(ctx);
}

void
zink_blit_barriers(struct zink_context *ctx, struct zink_resource *src, struct zink_resource *dst, bool whole_dst)
{
   struct zink_screen *screen = zink_screen(ctx->base.screen);
   if (src && zink_is_swapchain(src)) {
      if (!zink_kopper_acquire(ctx, src, UINT64_MAX))
         return;
   } else if (dst && zink_is_swapchain(dst)) {
      if (!zink_kopper_acquire(ctx, dst, UINT64_MAX))
         return;
   }

   VkAccessFlagBits flags;
   VkPipelineStageFlagBits pipeline;
   if (util_format_is_depth_or_stencil(dst->base.b.format)) {
      flags = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
      if (!whole_dst)
         flags |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT;
      pipeline = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
   } else {
      flags = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
      if (!whole_dst)
         flags |= VK_ACCESS_COLOR_ATTACHMENT_READ_BIT;
      pipeline = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
   }
   if (src == dst) {
      VkImageLayout layout = zink_screen(ctx->base.screen)->info.have_EXT_attachment_feedback_loop_layout ?
                             VK_IMAGE_LAYOUT_ATTACHMENT_FEEDBACK_LOOP_OPTIMAL_EXT :
                             VK_IMAGE_LAYOUT_GENERAL;
      screen->image_barrier(ctx, src, layout, VK_ACCESS_SHADER_READ_BIT | flags, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | pipeline);
   } else {
      if (src) {
         VkImageLayout layout = util_format_is_depth_or_stencil(src->base.b.format) &&
                                src->obj->vkusage & VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT ?
                                VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL :
                                VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
         screen->image_barrier(ctx, src, layout,
                              VK_ACCESS_SHADER_READ_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT);
         if (!ctx->unordered_blitting)
            src->obj->unordered_read = false;
      }
      VkImageLayout layout = util_format_is_depth_or_stencil(dst->base.b.format) ?
                           VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL :
                           VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
      screen->image_barrier(ctx, dst, layout, flags, pipeline);
   }
   if (!ctx->unordered_blitting)
      dst->obj->unordered_read = dst->obj->unordered_write = false;
}

bool
zink_blit_region_fills(struct u_rect region, unsigned width, unsigned height)
{
   struct u_rect intersect = {0, width, 0, height};
   struct u_rect r = {
      MIN2(region.x0, region.x1),
      MAX2(region.x0, region.x1),
      MIN2(region.y0, region.y1),
      MAX2(region.y0, region.y1),
   };

   if (!u_rect_test_intersection(&r, &intersect))
      /* is this even a thing? */
      return false;

   u_rect_find_intersection(&r, &intersect);
   if (intersect.x0 != 0 || intersect.y0 != 0 ||
       intersect.x1 != width || intersect.y1 != height)
      return false;

   return true;
}

bool
zink_blit_region_covers(struct u_rect region, struct u_rect covers)
{
   struct u_rect r = {
      MIN2(region.x0, region.x1),
      MAX2(region.x0, region.x1),
      MIN2(region.y0, region.y1),
      MAX2(region.y0, region.y1),
   };
   struct u_rect c = {
      MIN2(covers.x0, covers.x1),
      MAX2(covers.x0, covers.x1),
      MIN2(covers.y0, covers.y1),
      MAX2(covers.y0, covers.y1),
   };
   struct u_rect intersect;
   if (!u_rect_test_intersection(&r, &c))
      return false;

    u_rect_union(&intersect, &r, &c);
    return intersect.x0 == c.x0 && intersect.y0 == c.y0 &&
           intersect.x1 == c.x1 && intersect.y1 == c.y1;
}

void
zink_draw_rectangle(struct blitter_context *blitter, void *vertex_elements_cso,
                    blitter_get_vs_func get_vs, int x1, int y1, int x2, int y2,
                    float depth, unsigned num_instances, enum blitter_attrib_type type,
                    const union blitter_attrib *attrib)
{
   struct zink_context *ctx = zink_context(blitter->pipe);

   union blitter_attrib new_attrib = *attrib;

   /* Avoid inconsistencies in rounding between both triangles which can show with
    * nearest filtering by expanding the rect so only one triangle is effectively drawn.
    */
   if (ctx->blit_scissor && ctx->blit_nearest) {
      int64_t new_x1 = (int64_t)x1 * 2 - x2;
      int64_t new_y2 = (int64_t)y2 * 2 - y1;
      if (new_x1 < INT32_MAX && new_x1 > INT32_MIN &&
          new_y2 < INT32_MAX && new_y2 > INT32_MIN) {
         x1 = new_x1;
         y2 = new_y2;

         if (type == UTIL_BLITTER_ATTRIB_TEXCOORD_XY ||
             type == UTIL_BLITTER_ATTRIB_TEXCOORD_XYZW) {
            new_attrib.texcoord.x1 += new_attrib.texcoord.x1 - new_attrib.texcoord.x2;
            new_attrib.texcoord.y2 += new_attrib.texcoord.y2 - new_attrib.texcoord.y1;
         }
      }
   }

   util_blitter_draw_rectangle(blitter, vertex_elements_cso, get_vs, x1, y1, x2, y2,
                               depth, num_instances, type, &new_attrib);
}
