/*
 * Copyright (c) 2008-2024 Broadcom. All Rights Reserved.
 * The term “Broadcom” refers to Broadcom Inc.
 * and/or its subsidiaries.
 * SPDX-License-Identifier: MIT
 */

#include "svga_cmd.h"

#include "pipe/p_state.h"
#include "pipe/p_defines.h"
#include "util/u_inlines.h"
#include "util/u_thread.h"
#include "util/u_bitmask.h"
#include "util/format/u_format.h"
#include "util/u_math.h"
#include "util/u_memory.h"

#include "svga_format.h"
#include "svga_screen.h"
#include "svga_context.h"
#include "svga_sampler_view.h"
#include "svga_resource_texture.h"
#include "svga_surface.h"
#include "svga_debug.h"

static void svga_mark_surface_dirty(struct svga_surface *s);

void
svga_texture_copy_region(struct svga_context *svga,
                         struct svga_winsys_surface *src_handle,
                         unsigned srcSubResource,
                         unsigned src_x, unsigned src_y, unsigned src_z,
                         struct svga_winsys_surface *dst_handle,
                         unsigned dstSubResource,
                         unsigned dst_x, unsigned dst_y, unsigned dst_z,
                         unsigned width, unsigned height, unsigned depth)
{
   SVGA3dCopyBox box;

   assert(svga_have_vgpu10(svga));

   box.x = dst_x;
   box.y = dst_y;
   box.z = dst_z;
   box.w = width;
   box.h = height;
   box.d = depth;
   box.srcx = src_x;
   box.srcy = src_y;
   box.srcz = src_z;

   SVGA_RETRY(svga, SVGA3D_vgpu10_PredCopyRegion
              (svga->swc, dst_handle, dstSubResource,
               src_handle, srcSubResource, &box));
}


void
svga_texture_copy_handle(struct svga_context *svga,
                         struct svga_winsys_surface *src_handle,
                         unsigned src_x, unsigned src_y, unsigned src_z,
                         unsigned src_level, unsigned src_layer,
                         struct svga_winsys_surface *dst_handle,
                         unsigned dst_x, unsigned dst_y, unsigned dst_z,
                         unsigned dst_level, unsigned dst_layer,
                         unsigned width, unsigned height, unsigned depth)
{
   struct svga_surface dst = { 0 }, src = { 0 };
   SVGA3dCopyBox box, *boxes;

   assert(svga);

   src.handle = src_handle;
   src.real_level = src_level;
   src.real_layer = src_layer;
   src.real_zslice = 0;

   dst.handle = dst_handle;
   dst.real_level = dst_level;
   dst.real_layer = dst_layer;
   dst.real_zslice = 0;

   box.x = dst_x;
   box.y = dst_y;
   box.z = dst_z;
   box.w = width;
   box.h = height;
   box.d = depth;
   box.srcx = src_x;
   box.srcy = src_y;
   box.srcz = src_z;

/*
   SVGA_DBG(DEBUG_VIEWS, "mipcopy src: %p %u (%ux%ux%u), dst: %p %u (%ux%ux%u)\n",
            src_handle, src_level, src_x, src_y, src_z,
            dst_handle, dst_level, dst_x, dst_y, dst_z);
*/

   SVGA_RETRY(svga, SVGA3D_BeginSurfaceCopy(svga->swc, &src, &dst, &boxes, 1));
   *boxes = box;
   SVGA_FIFOCommitAll(svga->swc);
}


/* A helper function to sync up the two surface handles.
 */
static void
svga_texture_copy_handle_resource(struct svga_context *svga,
                                  struct svga_texture *src_tex,
                                  struct svga_winsys_surface *dst,
                                  unsigned int numMipLevels,
                                  unsigned int numLayers,
                                  int zslice_pick,
                                  unsigned int mipoffset,
                                  unsigned int layeroffset)
{
   unsigned int zoffset = 0;

   /* A negative zslice_pick implies zoffset at 0, and depth to copy is
    * from the depth of the texture at the particular mipmap level.
    */
   if (zslice_pick >= 0)
      zoffset = zslice_pick;

   for (unsigned i = 0; i < numMipLevels; i++) {
      unsigned int miplevel = i + mipoffset;

      for (unsigned j = 0; j < numLayers; j++) {
         if (svga_is_texture_level_defined(src_tex, j+layeroffset, miplevel)) {
            unsigned depth = (zslice_pick < 0 ?
                              u_minify(src_tex->b.depth0, miplevel) : 1);

            if (src_tex->b.nr_samples > 1) {
               unsigned subResource = j * numMipLevels + i;
               svga_texture_copy_region(svga, src_tex->handle,
                                        subResource, 0, 0, zoffset,
                                        dst, subResource, 0, 0, 0,
                                        src_tex->b.width0, src_tex->b.height0, depth);
            }
            else {
               svga_texture_copy_handle(svga,
                                        src_tex->handle,
                                        0, 0, zoffset,
                                        miplevel,
                                        j + layeroffset,
                                        dst, 0, 0, 0, i, j,
                                        u_minify(src_tex->b.width0, miplevel),
                                        u_minify(src_tex->b.height0, miplevel),
                                        depth);
            }
         }
      }
   }
}


struct svga_winsys_surface *
svga_texture_view_surface(struct svga_context *svga,
                          struct svga_texture *tex,
                          unsigned bind_flags,
                          SVGA3dSurfaceAllFlags flags,
                          SVGA3dSurfaceFormat format,
                          unsigned start_mip,
                          unsigned num_mip,
                          int layer_pick,
                          unsigned num_layers,
                          int zslice_pick,
                          bool cacheable,
                          struct svga_host_surface_cache_key *key) /* OUT */
{
   struct svga_screen *ss = svga_screen(svga->pipe.screen);
   struct svga_winsys_surface *handle = NULL;

   SVGA_DBG(DEBUG_PERF,
            "svga: Create surface view: layer %d zslice %d mips %d..%d\n",
            layer_pick, zslice_pick, start_mip, start_mip+num_mip-1);

   SVGA_STATS_TIME_PUSH(ss->sws, SVGA_STATS_TIME_EMULATESURFACEVIEW);

   key->flags = flags;
   key->format = format;
   key->numMipLevels = num_mip;
   key->size.width = u_minify(tex->b.width0, start_mip);
   key->size.height = u_minify(tex->b.height0, start_mip);
   key->size.depth = zslice_pick < 0 ? u_minify(tex->b.depth0, start_mip) : 1;
   key->cachable = 1;
   key->arraySize = 1;
   key->numFaces = 1;

   /* single sample surface can be treated as non-multisamples surface */
   key->sampleCount = tex->b.nr_samples > 1 ? tex->b.nr_samples : 0;

   if (key->sampleCount > 1) {
      assert(ss->sws->have_sm4_1);
      key->flags |= SVGA3D_SURFACE_MULTISAMPLE;
   }

   if (tex->b.target == PIPE_TEXTURE_CUBE && layer_pick < 0) {
      key->flags |= SVGA3D_SURFACE_CUBEMAP;
      key->numFaces = 6;
   } else if (tex->b.target == PIPE_TEXTURE_1D_ARRAY ||
              tex->b.target == PIPE_TEXTURE_2D_ARRAY) {
      key->arraySize = num_layers;
   }

   if (key->format == SVGA3D_FORMAT_INVALID) {
      key->cachable = 0;
      goto done;
   }

   bool needCopyResource;
   bool invalidated;
   if (cacheable && tex->backed_handle &&
       memcmp(key, &tex->backed_key, sizeof *key) == 0) {
      handle = tex->backed_handle;
      needCopyResource = tex->backed_age < tex->age;
   } else {
      SVGA_DBG(DEBUG_DMA, "surface_create for texture view\n");
      handle = svga_screen_surface_create(ss, bind_flags, PIPE_USAGE_DEFAULT,
                                          &invalidated, key);
      needCopyResource = true;

      if (cacheable && !tex->backed_handle) {
         tex->backed_handle = handle;
         memcpy(&tex->backed_key, key, sizeof *key);
      }
   }

   if (!handle) {
      key->cachable = 0;
      goto done;
   }

   SVGA_DBG(DEBUG_DMA, " --> got sid %p (texture view)\n", handle);

   if (layer_pick < 0)
      layer_pick = 0;

   if (needCopyResource) {
      svga_texture_copy_handle_resource(svga, tex, handle,
                                        key->numMipLevels,
                                        key->numFaces * key->arraySize,
                                        zslice_pick, start_mip, layer_pick);
      tex->backed_age = tex->age;
   }

done:
   SVGA_STATS_TIME_POP(ss->sws);

   return handle;
}


/**
 * A helper function to create a surface view.
 * The clone_resource boolean flag specifies whether to clone the resource
 * for the surface view.
 */
static struct pipe_surface *
svga_create_surface_view(struct pipe_context *pipe,
                         struct pipe_resource *pt,
                         const struct pipe_surface *surf_tmpl,
                         bool clone_resource)
{
   struct svga_context *svga = svga_context(pipe);
   struct svga_texture *tex = svga_texture(pt);
   struct pipe_screen *screen = pipe->screen;
   struct svga_screen *ss = svga_screen(screen);
   struct pipe_surface *retVal = NULL;

   struct svga_surface *s = CALLOC_STRUCT(svga_surface);
   if (!s)
      return NULL;

   SVGA_STATS_TIME_PUSH(ss->sws, SVGA_STATS_TIME_CREATESURFACEVIEW);

   unsigned layer, zslice, nlayers = 1;
   if (pt->target == PIPE_TEXTURE_CUBE) {
      layer = surf_tmpl->first_layer;
      zslice = 0;
   }
   else if (pt->target == PIPE_TEXTURE_1D_ARRAY ||
            pt->target == PIPE_TEXTURE_2D_ARRAY ||
            pt->target == PIPE_TEXTURE_CUBE_ARRAY) {
      layer = surf_tmpl->first_layer;
      zslice = 0;
      nlayers = surf_tmpl->last_layer - surf_tmpl->first_layer + 1;
   }
   else {
      layer = 0;
      zslice = surf_tmpl->first_layer;
   }

   pipe_reference_init(&s->base.reference, 1);
   pipe_resource_reference(&s->base.texture, pt);
   s->context = pipe;
   s->base.format = surf_tmpl->format;
   s->base.level = surf_tmpl->level;
   s->base.first_layer = surf_tmpl->first_layer;
   s->base.last_layer = surf_tmpl->last_layer;
   s->view_id = SVGA3D_INVALID_ID;

   s->backed = NULL;

   SVGA3dSurfaceAllFlags flags;
   unsigned bind;
   if (util_format_is_depth_or_stencil(surf_tmpl->format)) {
      flags = SVGA3D_SURFACE_HINT_DEPTHSTENCIL |
              SVGA3D_SURFACE_BIND_DEPTH_STENCIL;
      bind = PIPE_BIND_DEPTH_STENCIL;
   }
   else {
      flags = SVGA3D_SURFACE_HINT_RENDERTARGET |
              SVGA3D_SURFACE_BIND_RENDER_TARGET;
      bind = PIPE_BIND_RENDER_TARGET;
   }

   SVGA3dSurfaceFormat format;
   if (tex->imported) {
      /* imported resource (a window) */
      format = tex->key.format;
      if (util_format_is_srgb(surf_tmpl->format)) {
         /* sRGB rendering to window */
         format = svga_linear_to_srgb(format);
      }
   }
   else {
      format = svga_translate_format(ss, surf_tmpl->format, bind);
   }

   assert(format != SVGA3D_FORMAT_INVALID);

   if (clone_resource) {
      SVGA_DBG(DEBUG_VIEWS,
               "New backed surface view: resource %p, level %u layer %u z %u, %p\n",
               pt, surf_tmpl->level, layer, zslice, s);

      if (svga_have_vgpu10(svga)) {
         switch (pt->target) {
         case PIPE_TEXTURE_1D:
            flags |= SVGA3D_SURFACE_1D;
            break;
         case PIPE_TEXTURE_1D_ARRAY:
            flags |= SVGA3D_SURFACE_1D | SVGA3D_SURFACE_ARRAY;
            break;
         case PIPE_TEXTURE_2D_ARRAY:
            flags |= SVGA3D_SURFACE_ARRAY;
            break;
         case PIPE_TEXTURE_3D:
            flags |= SVGA3D_SURFACE_VOLUME;
            break;
         case PIPE_TEXTURE_CUBE:
            if (nlayers == 6)
               flags |= SVGA3D_SURFACE_CUBEMAP;
            break;
         case PIPE_TEXTURE_CUBE_ARRAY:
            if (nlayers % 6 == 0)
               flags |= SVGA3D_SURFACE_CUBEMAP | SVGA3D_SURFACE_ARRAY;
            break;
         default:
            break;
         }
      }

      /* When we clone the surface view resource, use the format used in
       * the creation of the original resource.
       */
      s->handle = svga_texture_view_surface(svga, tex, bind, flags,
                                            tex->key.format,
                                            surf_tmpl->level, 1,
                                            layer, nlayers, zslice,
                                            true, &s->key);
      if (!s->handle) {
         FREE(s);
         goto done;
      }

      s->key.format = format;
      s->real_layer = 0;
      s->real_level = 0;
      s->real_zslice = 0;
   } else {
      SVGA_DBG(DEBUG_VIEWS,
               "New surface view: resource %p, level %u, layer %u, z %u, %p\n",
               pt, surf_tmpl->level, layer, zslice, s);

      memset(&s->key, 0, sizeof s->key);
      s->key.format = format;
      s->handle = tex->handle;
      s->real_layer = layer;
      s->real_zslice = zslice;
      s->real_level = surf_tmpl->level;
   }

   svga->hud.num_surface_views++;
   retVal = &s->base;

done:
   SVGA_STATS_TIME_POP(ss->sws);
   return retVal;
}


struct pipe_surface *
svga_create_surface(struct pipe_context *pipe,
                    struct pipe_resource *pt,
                    const struct pipe_surface *surf_tmpl)
{
   struct svga_context *svga = svga_context(pipe);
   struct pipe_screen *screen = pipe->screen;
   bool view = false;

   SVGA_STATS_TIME_PUSH(svga_sws(svga), SVGA_STATS_TIME_CREATESURFACE);

   if (svga_screen(screen)->debug.force_surface_view)
      view = true;

   if (surf_tmpl->level != 0 &&
       svga_screen(screen)->debug.force_level_surface_view)
      view = true;

   if (pt->target == PIPE_TEXTURE_3D)
      view = true;

   if (svga_have_vgpu10(svga) || svga_screen(screen)->debug.no_surface_view)
      view = false;

   struct pipe_surface *surf = svga_create_surface_view(pipe, pt, surf_tmpl, view);

   SVGA_STATS_TIME_POP(svga_sws(svga));

   return surf;
}


/**
 * Create an alternate surface view and clone the resource if specified
 */
static struct svga_surface *
create_backed_surface_view(struct svga_context *svga, struct svga_surface *s,
                           bool clone_resource)
{
   struct svga_texture *tex = svga_texture(s->base.texture);

   if (!s->backed) {
      SVGA_STATS_TIME_PUSH(svga_sws(svga),
                           SVGA_STATS_TIME_CREATEBACKEDSURFACEVIEW);

      struct pipe_surface *backed_view =
         svga_create_surface_view(&svga->pipe, &tex->b,
                                  &s->base, clone_resource);
      if (!backed_view)
         goto done;

      s->backed = svga_surface(backed_view);

      SVGA_STATS_TIME_POP(svga_sws(svga));
   }
   else if (s->backed->handle != tex->handle &&
            s->backed->age < tex->age) {
      /*
       * There is already an existing backing surface, but we still need to
       * sync the backing resource if the original resource has been modified
       * since the last copy.
       */
      struct svga_surface *bs = s->backed;
      unsigned int layer, zslice;

      assert(bs->handle);

      switch (tex->b.target) {
      case PIPE_TEXTURE_CUBE:
      case PIPE_TEXTURE_CUBE_ARRAY:
      case PIPE_TEXTURE_1D_ARRAY:
      case PIPE_TEXTURE_2D_ARRAY:
         layer = s->base.first_layer;
         zslice = 0;
         break;
      default:
         layer = 0;
         zslice = s->base.first_layer;
      }

      svga_texture_copy_handle_resource(svga, tex, bs->handle,
                                        bs->key.numMipLevels,
                                        bs->key.numFaces * bs->key.arraySize,
                                        zslice, s->base.level, layer);
   }

   svga_mark_surface_dirty(s->backed);
   s->backed->age = tex->age;

   assert(s->backed->context == &svga->pipe);

done:
   return s->backed;
}


/**
 * Create a DX RenderTarget/DepthStencil View for the given surface,
 * if needed.
 */
struct pipe_surface *
svga_validate_surface_view(struct svga_context *svga, struct svga_surface *s)
{
   enum pipe_error ret = PIPE_OK;
   mesa_shader_stage shader;

   assert(svga_have_vgpu10(svga));
   assert(s);

   SVGA_STATS_TIME_PUSH(svga_sws(svga),
                        SVGA_STATS_TIME_VALIDATESURFACEVIEW);

   /**
    * DX spec explicitly specifies that no resource can be bound to a render
    * target view and a shader resource view simultaneously.
    * So first check if the resource bound to this surface view collides with
    * a sampler view. If so, then we will clone this surface view and its
    * associated resource. We will then use the cloned surface view for
    * render target.
    */
   for (shader = MESA_SHADER_VERTEX; shader <= MESA_SHADER_COMPUTE; shader++) {
      if (svga_check_sampler_view_resource_collision(svga, s->handle, shader)) {
         SVGA_DBG(DEBUG_VIEWS,
                  "same resource used in shaderResource and renderTarget 0x%x\n",
                  s->handle);
         s = create_backed_surface_view(svga, s, true);

         if (s)
            svga->state.hw_draw.has_backed_views = true;

         /* s may be null here if the function failed */
         break;
      }
   }

   /**
    * Create an alternate surface view for the specified context if the
    * view was created for another context.
    */
   if (s && s->context != &svga->pipe) {
      s = create_backed_surface_view(svga, s, false);

      if (s)
         svga->state.hw_draw.has_backed_views = true;
   }

   if (s && s->view_id == SVGA3D_INVALID_ID) {
      SVGA3dResourceType resType;
      SVGA3dRenderTargetViewDesc desc;
      struct svga_texture *stex = svga_texture(s->base.texture);

      if (stex->surface_state < SVGA_SURFACE_STATE_INVALIDATED) {
         assert(stex->handle);

         /* We are about to render into a surface that has not been validated.
          * First invalidate the surface so that the device does not
          * need to update the host-side copy with the invalid
          * content when the associated mob is first bound to the surface.
          */
         SVGA_RETRY(svga, SVGA3D_InvalidateGBSurface(svga->swc, stex->handle));
         stex->surface_state = SVGA_SURFACE_STATE_INVALIDATED;
      }

      desc.tex.mipSlice = s->real_level;
      desc.tex.firstArraySlice = s->real_layer + s->real_zslice;
      desc.tex.arraySize =
         s->base.last_layer - s->base.first_layer + 1;

      resType = svga_resource_type(s->base.texture->target);

      if (util_format_is_depth_or_stencil(s->base.format)) {

         /* Create depth stencil view only if the resource is created
          * with depth stencil bind flag.
          */
         if (stex->key.flags & SVGA3D_SURFACE_BIND_DEPTH_STENCIL) {
            s->view_id = util_bitmask_add(svga->surface_view_id_bm);
            ret = SVGA3D_vgpu10_DefineDepthStencilView(svga->swc,
                                                       s->view_id,
                                                       s->handle,
                                                       s->key.format,
                                                       resType,
                                                       &desc);
         }
      }
      else {
         /* Create render target view only if the resource is created
          * with render target bind flag.
          */
         if (stex->key.flags & SVGA3D_SURFACE_BIND_RENDER_TARGET) {
            SVGA3dSurfaceFormat view_format = s->key.format;

            /* Can't create RGBA render target view of a RGBX surface so adjust
             * the view format.  We do something similar for texture samplers in
             * svga_validate_pipe_sampler_view().
             */
            if (view_format == SVGA3D_B8G8R8A8_UNORM &&
                (stex->key.format == SVGA3D_B8G8R8X8_UNORM ||
                 stex->key.format == SVGA3D_B8G8R8X8_TYPELESS)) {
               view_format = SVGA3D_B8G8R8X8_UNORM;
            }

            s->view_id = util_bitmask_add(svga->surface_view_id_bm);
            ret = SVGA3D_vgpu10_DefineRenderTargetView(svga->swc,
                                                       s->view_id,
                                                       s->handle,
                                                       view_format,
                                                       resType,
                                                       &desc);
         }
      }

      if (ret != PIPE_OK) {
         util_bitmask_clear(svga->surface_view_id_bm, s->view_id);
         s->view_id = SVGA3D_INVALID_ID;
         s = NULL;
      }
   }

   SVGA_STATS_TIME_POP(svga_sws(svga));

   return s ? &s->base : NULL;
}


void
svga_surface_destroy(struct pipe_context *pipe,
                     struct pipe_surface *surf)
{
   struct svga_context *svga = svga_context(pipe);
   struct svga_surface *s = svga_surface(surf);
   struct svga_texture *t = svga_texture(surf->texture);
   struct svga_screen *ss = svga_screen(surf->texture->screen);

   SVGA_STATS_TIME_PUSH(ss->sws, SVGA_STATS_TIME_DESTROYSURFACE);

   /* Destroy the backed view surface if it exists */
   if (s->backed) {
      svga_surface_unref(pipe, &s->backed);
   }

   /* Destroy the surface handle if this is a backed handle and
    * it is not being cached in the texture.
    */
   if (s->handle != t->handle && s->handle != t->backed_handle) {
      SVGA_DBG(DEBUG_DMA, "unref sid %p (tex surface)\n", s->handle);
      svga_screen_surface_destroy(ss, &s->key,
                                  svga_was_texture_rendered_to(t),
                                  &s->handle);
   }

   if (s->view_id != SVGA3D_INVALID_ID) {
      /* The SVGA3D device will generate a device error if the
       * render target view or depth stencil view is destroyed from
       * a context other than the one it was created with.
       * Similar to shader resource view, in this case, we will skip
       * the destroy for now.
       */
      if (s->context != pipe) {
         _debug_printf("context mismatch in %s\n", __func__);
      }
      else {
         assert(svga_have_vgpu10(svga));
         if (util_format_is_depth_or_stencil(s->base.format)) {
            SVGA_RETRY(svga, SVGA3D_vgpu10_DestroyDepthStencilView(svga->swc,
                                                                   s->view_id));
         }
         else {
            SVGA_RETRY(svga, SVGA3D_vgpu10_DestroyRenderTargetView(svga->swc,
                                                                   s->view_id));
         }
         util_bitmask_clear(svga->surface_view_id_bm, s->view_id);
      }
   }

   pipe_resource_reference(&surf->texture, NULL);
   FREE(surf);

   svga->hud.num_surface_views--;
   SVGA_STATS_TIME_POP(ss->sws);
}


static void
svga_mark_surface_dirty(struct svga_surface *s)
{
   struct svga_texture *tex = svga_texture(s->base.texture);

   if (!s->dirty) {
      s->dirty = true;

      if (s->handle == tex->handle) {
         /* hmm so 3d textures always have all their slices marked ? */
         svga_define_texture_level(tex, s->base.first_layer,
                                   s->base.level);
      }
      else {
         /* this will happen later in svga_propagate_surface */
      }
   }

   /* Increment the view_age and texture age for this surface's mipmap
    * level so that any sampler views into the texture are re-validated too.
    * Note: we age the texture for backed surface view only when the
    *       backed surface is propagated to the original surface.
    */
   if (s->handle == tex->handle)
      svga_age_texture_view(tex, s->base.level);
}


void
svga_mark_surfaces_dirty(struct svga_context *svga)
{
   struct svga_hw_clear_state *hw = &svga->state.hw_clear;

   if (svga_have_vgpu10(svga)) {
      /* For VGPU10, mark the dirty bit in the rendertarget/depth stencil view surface.
       * This surface can be the backed surface.
       */
      for (unsigned i = 0; i < hw->num_rendertargets; i++) {
         if (hw->rtv[i])
            svga_mark_surface_dirty(svga_surface(hw->rtv[i]));
      }
      if (hw->dsv)
         svga_mark_surface_dirty(svga_surface(hw->dsv));
   } else {
      for (unsigned i = 0; i < svga->curr.framebuffer.base.nr_cbufs; i++) {
         if (svga->curr.framebuffer.base.cbufs[i].texture)
            svga_mark_surface_dirty(svga->curr.framebuffer.cbufs[i]);
      }
      if (svga->curr.framebuffer.base.zsbuf.texture)
         svga_mark_surface_dirty(svga->curr.framebuffer.zsbuf);
   }
}


/**
 * Progagate any changes from surfaces to texture.
 * pipe is optional context to inline the blit command in.
 */
void
svga_propagate_surface(struct svga_context *svga, struct svga_surface *s,
                       bool reset)
{
   struct svga_texture *tex = svga_texture(s->base.texture);
   struct svga_screen *ss = svga_screen(s->base.texture->screen);

   if (!s->dirty)
      return;

   SVGA_STATS_TIME_PUSH(ss->sws, SVGA_STATS_TIME_PROPAGATESURFACE);

   /* Reset the dirty flag if specified. This is to ensure that
    * the dirty flag will not be reset and stay unset when the backing
    * surface is still being bound and rendered to.
    * The reset flag will be set to TRUE when the surface is propagated
    * and will be unbound.
    */
   s->dirty = !reset;

   ss->texture_timestamp++;
   svga_age_texture_view(tex, s->base.level);

   if (s->handle != tex->handle) {
      unsigned zslice, layer;
      unsigned nlayers = 1;
      const unsigned numMipLevels = tex->b.last_level + 1;
      const unsigned srcLevel = s->real_level;
      const unsigned dstLevel = s->base.level;
      const unsigned width = u_minify(tex->b.width0, dstLevel);
      const unsigned height = u_minify(tex->b.height0, dstLevel);

      if (s->base.texture->target == PIPE_TEXTURE_CUBE) {
         zslice = 0;
         layer = s->base.first_layer;
      }
      else if (s->base.texture->target == PIPE_TEXTURE_1D_ARRAY ||
               s->base.texture->target == PIPE_TEXTURE_2D_ARRAY ||
               s->base.texture->target == PIPE_TEXTURE_CUBE_ARRAY) {
         zslice = 0;
         layer = s->base.first_layer;
         nlayers = s->base.last_layer - s->base.first_layer + 1;
      }
      else {
         zslice = s->base.first_layer;
         layer = 0;
      }

      SVGA_DBG(DEBUG_VIEWS,
               "Propagate surface %p to resource %p, level %u\n",
               s, tex, s->base.level);

      if (svga_have_vgpu10(svga)) {
         unsigned srcSubResource, dstSubResource;

         for (unsigned i = 0; i < nlayers; i++) {
            srcSubResource = (s->real_layer + i) * numMipLevels + srcLevel;
            dstSubResource = (layer + i) * numMipLevels + dstLevel;

            svga_texture_copy_region(svga,
                                     s->handle, srcSubResource, 0, 0, 0,
                                     tex->handle, dstSubResource, 0, 0, zslice,
                                     width, height, 1);
            svga_define_texture_level(tex, layer + i, dstLevel);
         }
      }
      else {
         for (unsigned i = 0; i < nlayers; i++) {
            svga_texture_copy_handle(svga,
                                     s->handle, 0, 0, 0, srcLevel,
                                     s->real_layer + i,
                                     tex->handle, 0, 0, zslice, dstLevel,
                                     layer + i,
                                     width, height, 1);

            svga_define_texture_level(tex, layer + i, dstLevel);
         }
      }

      /* Sync the surface view age with the texture age */
      s->age = tex->age;

      /* If this backed surface is cached in the texture,
       * update the backed age as well.
       */
      if (tex->backed_handle == s->handle) {
         tex->backed_age = tex->age;
      }
   }

   SVGA_STATS_TIME_POP(ss->sws);
}


/**
 * If any of the render targets are in backing texture views, propagate any
 * changes to them back to the original texture.
 */
void
svga_propagate_rendertargets(struct svga_context *svga)
{
   /* Early exit if there is no backing texture views in use */
   if (!svga->state.hw_draw.has_backed_views)
      return;

   /* Note that we examine the svga->state.hw_draw.framebuffer surfaces,
    * not the svga->curr.framebuffer surfaces, because it's the former
    * surfaces which may be backing surface views (the actual render targets).
    */
   for (unsigned i = 0; i < svga->state.hw_clear.num_rendertargets; i++) {
      struct svga_surface *s = svga_surface(svga->state.hw_clear.rtv[i]);
      if (s) {
         svga_propagate_surface(svga, s, false);
      }
   }

   if (svga->state.hw_clear.dsv) {
      svga_propagate_surface(svga, svga_surface(svga->state.hw_clear.dsv), false);
   }
}


/**
 * Check if we should call svga_propagate_surface on the surface.
 */
bool
svga_surface_needs_propagation(const struct svga_surface *s)
{
   if (s == NULL) {
      return false;
   } else {
      struct svga_texture *tex = svga_texture(s->base.texture);
      return s->dirty && s->handle != tex->handle;
   }
}


static void
svga_get_sample_position(struct pipe_context *context,
                         unsigned sample_count, unsigned sample_index,
                         float *pos_out)
{
   /* We can't actually query the device to learn the sample positions.
    * These were grabbed from nvidia's driver.
    */
   static const float pos1[1][2] = {
      { 0.5, 0.5 }
   };
   static const float pos2[2][2] = {
      { 0.75, 0.75 },
      { 0.25, 0.25 }
   };
   static const float pos4[4][2] = {
      { 0.375000, 0.125000 },
      { 0.875000, 0.375000 },
      { 0.125000, 0.625000 },
      { 0.625000, 0.875000 }
   };
   static const float pos8[8][2] = {
      { 0.562500, 0.312500 },
      { 0.437500, 0.687500 },
      { 0.812500, 0.562500 },
      { 0.312500, 0.187500 },
      { 0.187500, 0.812500 },
      { 0.062500, 0.437500 },
      { 0.687500, 0.937500 },
      { 0.937500, 0.062500 }
   };
   static const float pos16[16][2] = {
      { 0.187500, 0.062500 },
      { 0.437500, 0.187500 },
      { 0.062500, 0.312500 },
      { 0.312500, 0.437500 },
      { 0.687500, 0.062500 },
      { 0.937500, 0.187500 },
      { 0.562500, 0.312500 },
      { 0.812500, 0.437500 },
      { 0.187500, 0.562500 },
      { 0.437500, 0.687500 },
      { 0.062500, 0.812500 },
      { 0.312500, 0.937500 },
      { 0.687500, 0.562500 },
      { 0.937500, 0.687500 },
      { 0.562500, 0.812500 },
      { 0.812500, 0.937500 }
   };
   const float (*positions)[2];

   switch (sample_count) {
   case 2:
      positions = pos2;
      break;
   case 4:
      positions = pos4;
      break;
   case 8:
      positions = pos8;
      break;
   case 16:
      positions = pos16;
      break;
   default:
      positions = pos1;
   }

   pos_out[0] = positions[sample_index][0];
   pos_out[1] = positions[sample_index][1];
}


void
svga_init_surface_functions(struct svga_context *svga)
{
   svga->pipe.get_sample_position = svga_get_sample_position;
}
