r300: respect radeon common code fallbacks
[mesa.git] / src / gallium / drivers / svga / svga_screen_buffer.c
index 58a1aba464b8fb4be3e29a0294414eb272082f9c..1ff6a3a5b317172dce122055b466ea8e52ce5eb8 100644 (file)
@@ -27,8 +27,8 @@
 
 #include "pipe/p_state.h"
 #include "pipe/p_defines.h"
-#include "pipe/p_inlines.h"
-#include "pipe/p_thread.h"
+#include "util/u_inlines.h"
+#include "os/os_thread.h"
 #include "util/u_math.h"
 #include "util/u_memory.h"
 
@@ -83,7 +83,7 @@ svga_buffer_create_host_surface(struct svga_screen *ss,
        * as svga_screen_surface_create might have passed a recycled host
        * buffer.
        */
-      sbuf->hw.flags.discard = TRUE;
+      sbuf->dma.flags.discard = TRUE;
 
       SVGA_DBG(DEBUG_DMA, "   --> got sid %p sz %d (buffer)\n", sbuf->handle, sbuf->base.size);
    }
@@ -109,72 +109,13 @@ svga_buffer_destroy_hw_storage(struct svga_screen *ss, struct svga_buffer *sbuf)
    struct svga_winsys_screen *sws = ss->sws;
 
    assert(!sbuf->map.count);
-   assert(sbuf->hw.buf);
-   if(sbuf->hw.buf) {
-      sws->buffer_destroy(sws, sbuf->hw.buf);
-      sbuf->hw.buf = NULL;
-      assert(sbuf->head.prev && sbuf->head.next);
-      LIST_DEL(&sbuf->head);
-#ifdef DEBUG
-      sbuf->head.next = sbuf->head.prev = NULL; 
-#endif
+   assert(sbuf->hwbuf);
+   if(sbuf->hwbuf) {
+      sws->buffer_destroy(sws, sbuf->hwbuf);
+      sbuf->hwbuf = NULL;
    }
 }
 
-static INLINE enum pipe_error
-svga_buffer_backup(struct svga_screen *ss, struct svga_buffer *sbuf)
-{
-   if (sbuf->hw.buf && sbuf->hw.num_ranges) {
-      void *src;
-
-      if (!sbuf->swbuf)
-        sbuf->swbuf = align_malloc(sbuf->base.size, sbuf->base.alignment);
-      if (!sbuf->swbuf)
-        return PIPE_ERROR_OUT_OF_MEMORY;
-
-      src = ss->sws->buffer_map(ss->sws, sbuf->hw.buf,
-                               PIPE_BUFFER_USAGE_CPU_READ);
-      if (!src)
-        return PIPE_ERROR;
-
-      memcpy(sbuf->swbuf, src, sbuf->base.size);
-      ss->sws->buffer_unmap(ss->sws, sbuf->hw.buf);
-   }
-
-   return PIPE_OK;
-}
-
-/**
- * Try to make GMR space available by freeing the hardware storage of 
- * unmapped
- */
-boolean
-svga_buffer_free_cached_hw_storage(struct svga_screen *ss)
-{
-   struct list_head *curr;
-   struct svga_buffer *sbuf;
-   enum pipe_error ret = PIPE_OK;
-
-   curr = ss->cached_buffers.prev;
-   
-   /* free the least recently used buffer's hw storage which is not mapped */
-   do {
-      if(curr == &ss->cached_buffers)
-         return FALSE;
-
-      sbuf = LIST_ENTRY(struct svga_buffer, curr, head);
-      
-      curr = curr->prev;
-      if (sbuf->map.count == 0)
-        ret = svga_buffer_backup(ss, sbuf);
-
-   } while(sbuf->map.count != 0 || ret != PIPE_OK);
-   
-   svga_buffer_destroy_hw_storage(ss, sbuf);
-   
-   return TRUE;
-}
-
 struct svga_winsys_buffer *
 svga_winsys_buffer_create( struct svga_screen *ss,
                            unsigned alignment, 
@@ -195,12 +136,6 @@ svga_winsys_buffer_create( struct svga_screen *ss,
       svga_screen_flush(ss, NULL);
       buf = sws->buffer_create(sws, alignment, usage, size);
 
-      SVGA_DBG(DEBUG_DMA|DEBUG_PERF, "evicting buffers to find %d bytes GMR\n", 
-               size);
-
-      /* Try evicing all buffer storage */
-      while(!buf && svga_buffer_free_cached_hw_storage(ss))
-         buf = sws->buffer_create(sws, alignment, usage, size);
    }
    
    return buf;
@@ -216,18 +151,18 @@ static INLINE enum pipe_error
 svga_buffer_create_hw_storage(struct svga_screen *ss,
                               struct svga_buffer *sbuf)
 {
-   if(!sbuf->hw.buf) {
+   assert(!sbuf->user);
+
+   if(!sbuf->hwbuf) {
       unsigned alignment = sbuf->base.alignment;
       unsigned usage = 0;
       unsigned size = sbuf->base.size;
       
-      sbuf->hw.buf = svga_winsys_buffer_create(ss, alignment, usage, size);
-      if(!sbuf->hw.buf)
+      sbuf->hwbuf = svga_winsys_buffer_create(ss, alignment, usage, size);
+      if(!sbuf->hwbuf)
          return PIPE_ERROR_OUT_OF_MEMORY;
       
-      assert(!sbuf->needs_flush);
-      assert(!sbuf->head.prev && !sbuf->head.next);
-      LIST_ADD(&sbuf->head, &ss->cached_buffers);
+      assert(!sbuf->dma.pending);
    }
    
    return PIPE_OK;
@@ -242,12 +177,11 @@ svga_buffer_upload_command(struct svga_context *svga,
                            struct svga_buffer *sbuf)
 {
    struct svga_winsys_context *swc = svga->swc;
-   struct svga_winsys_buffer *guest = sbuf->hw.buf;
+   struct svga_winsys_buffer *guest = sbuf->hwbuf;
    struct svga_winsys_surface *host = sbuf->handle;
    SVGA3dTransferType transfer = SVGA3D_WRITE_HOST_VRAM;
-   SVGA3dSurfaceDMAFlags flags = sbuf->hw.flags;
    SVGA3dCmdSurfaceDMA *cmd;
-   uint32 numBoxes = sbuf->hw.num_ranges;
+   uint32 numBoxes = sbuf->map.num_ranges;
    SVGA3dCopyBox *boxes;
    SVGA3dCmdSurfaceDMASuffix *pSuffix;
    unsigned region_flags;
@@ -285,8 +219,8 @@ svga_buffer_upload_command(struct svga_context *svga,
 
    cmd->transfer = transfer;
 
-   sbuf->hw.boxes = (SVGA3dCopyBox *)&cmd[1];
-   sbuf->hw.svga = svga;
+   sbuf->dma.boxes = (SVGA3dCopyBox *)&cmd[1];
+   sbuf->dma.svga = svga;
 
    /* Increment reference count */
    dummy = NULL;
@@ -295,9 +229,11 @@ svga_buffer_upload_command(struct svga_context *svga,
    pSuffix = (SVGA3dCmdSurfaceDMASuffix *)((uint8_t*)cmd + sizeof *cmd + numBoxes * sizeof *boxes);
    pSuffix->suffixSize = sizeof *pSuffix;
    pSuffix->maximumOffset = sbuf->base.size;
-   pSuffix->flags = flags;
+   pSuffix->flags = sbuf->dma.flags;
+
+   SVGA_FIFOCommitAll(swc);
 
-   swc->commit(swc);
+   sbuf->dma.flags.discard = FALSE;
 
    return PIPE_OK;
 }
@@ -311,15 +247,14 @@ static void
 svga_buffer_upload_flush(struct svga_context *svga,
                          struct svga_buffer *sbuf)
 {
-   struct svga_screen *ss = svga_screen(svga->pipe.screen);
    SVGA3dCopyBox *boxes;
    unsigned i;
 
    assert(sbuf->handle); 
-   assert(sbuf->hw.buf);
-   assert(sbuf->hw.num_ranges);
-   assert(sbuf->hw.svga == svga);
-   assert(sbuf->hw.boxes);
+   assert(sbuf->hwbuf);
+   assert(sbuf->map.num_ranges);
+   assert(sbuf->dma.svga == svga);
+   assert(sbuf->dma.boxes);
    
    /*
     * Patch the DMA command with the final copy box.
@@ -327,33 +262,33 @@ svga_buffer_upload_flush(struct svga_context *svga,
 
    SVGA_DBG(DEBUG_DMA, "dma to sid %p\n", sbuf->handle);
 
-   boxes = sbuf->hw.boxes;
-   for(i = 0; i < sbuf->hw.num_ranges; ++i) {
+   boxes = sbuf->dma.boxes;
+   for(i = 0; i < sbuf->map.num_ranges; ++i) {
       SVGA_DBG(DEBUG_DMA, "  bytes %u - %u\n",
-               sbuf->hw.ranges[i].start, sbuf->hw.ranges[i].end);
+               sbuf->map.ranges[i].start, sbuf->map.ranges[i].end);
 
-      boxes[i].x = sbuf->hw.ranges[i].start;
+      boxes[i].x = sbuf->map.ranges[i].start;
       boxes[i].y = 0;
       boxes[i].z = 0;
-      boxes[i].w = sbuf->hw.ranges[i].end - sbuf->hw.ranges[i].start;
+      boxes[i].w = sbuf->map.ranges[i].end - sbuf->map.ranges[i].start;
       boxes[i].h = 1;
       boxes[i].d = 1;
-      boxes[i].srcx = sbuf->hw.ranges[i].start;
+      boxes[i].srcx = sbuf->map.ranges[i].start;
       boxes[i].srcy = 0;
       boxes[i].srcz = 0;
    }
 
-   sbuf->hw.num_ranges = 0;
-   memset(&sbuf->hw.flags, 0, sizeof sbuf->hw.flags);
+   sbuf->map.num_ranges = 0;
 
    assert(sbuf->head.prev && sbuf->head.next);
    LIST_DEL(&sbuf->head);
-   sbuf->needs_flush = FALSE;
-   /* XXX: do we care about cached_buffers any more ?*/
-   LIST_ADD(&sbuf->head, &ss->cached_buffers);
+#ifdef DEBUG
+   sbuf->head.next = sbuf->head.prev = NULL; 
+#endif
+   sbuf->dma.pending = FALSE;
 
-   sbuf->hw.svga = NULL;
-   sbuf->hw.boxes = NULL;
+   sbuf->dma.svga = NULL;
+   sbuf->dma.boxes = NULL;
 
    /* Decrement reference count */
    pipe_reference(&(sbuf->base.reference), NULL);
@@ -362,7 +297,7 @@ svga_buffer_upload_flush(struct svga_context *svga,
 
 
 /**
- * Queue a DMA upload of a range of this buffer to the host.
+ * Note a dirty range.
  *
  * This function only notes the range down. It doesn't actually emit a DMA
  * upload command. That only happens when a context tries to refer to this
@@ -371,15 +306,24 @@ svga_buffer_upload_flush(struct svga_context *svga,
  * We try to lump as many contiguous DMA transfers together as possible.
  */
 static void
-svga_buffer_upload_queue(struct svga_buffer *sbuf,
-                         unsigned start,
-                         unsigned end)
+svga_buffer_add_range(struct svga_buffer *sbuf,
+                      unsigned start,
+                      unsigned end)
 {
    unsigned i;
+   unsigned nearest_range;
+   unsigned nearest_dist;
 
-   assert(sbuf->hw.buf);
    assert(end > start);
    
+   if (sbuf->map.num_ranges < SVGA_BUFFER_MAX_RANGES) {
+      nearest_range = sbuf->map.num_ranges;
+      nearest_dist = ~0;
+   } else {
+      nearest_range = SVGA_BUFFER_MAX_RANGES - 1;
+      nearest_dist = 0;
+   }
+
    /*
     * Try to grow one of the ranges.
     *
@@ -390,12 +334,34 @@ svga_buffer_upload_queue(struct svga_buffer *sbuf,
     * buffer should be flushed.
     */
 
-   for(i = 0; i < sbuf->hw.num_ranges; ++i) {
-      if(start <= sbuf->hw.ranges[i].end && sbuf->hw.ranges[i].start <= end) {
-         sbuf->hw.ranges[i].start = MIN2(sbuf->hw.ranges[i].start, start);
-         sbuf->hw.ranges[i].end   = MAX2(sbuf->hw.ranges[i].end,    end);
+   for(i = 0; i < sbuf->map.num_ranges; ++i) {
+      int left_dist;
+      int right_dist;
+      int dist;
+
+      left_dist = start - sbuf->map.ranges[i].end;
+      right_dist = sbuf->map.ranges[i].start - end;
+      dist = MAX2(left_dist, right_dist);
+
+      if (dist <= 0) {
+         /*
+          * Ranges are contiguous or overlapping -- extend this one and return.
+          */
+
+         sbuf->map.ranges[i].start = MIN2(sbuf->map.ranges[i].start, start);
+         sbuf->map.ranges[i].end   = MAX2(sbuf->map.ranges[i].end,   end);
          return;
       }
+      else {
+         /*
+          * Discontiguous ranges -- keep track of the nearest range.
+          */
+
+         if (dist < nearest_dist) {
+            nearest_range = i;
+            nearest_dist = dist;
+         }
+      }
    }
 
    /*
@@ -403,20 +369,34 @@ svga_buffer_upload_queue(struct svga_buffer *sbuf,
     * pending DMA upload and start clean.
     */
 
-   if(sbuf->needs_flush)
-      svga_buffer_upload_flush(sbuf->hw.svga, sbuf);
+   if(sbuf->dma.pending)
+      svga_buffer_upload_flush(sbuf->dma.svga, sbuf);
 
-   assert(!sbuf->needs_flush);
-   assert(!sbuf->hw.svga);
-   assert(!sbuf->hw.boxes);
+   assert(!sbuf->dma.pending);
+   assert(!sbuf->dma.svga);
+   assert(!sbuf->dma.boxes);
 
-   /*
-    * Add a new range.
-    */
+   if (sbuf->map.num_ranges < SVGA_BUFFER_MAX_RANGES) {
+      /*
+       * Add a new range.
+       */
 
-   sbuf->hw.ranges[sbuf->hw.num_ranges].start = start;
-   sbuf->hw.ranges[sbuf->hw.num_ranges].end = end;
-   ++sbuf->hw.num_ranges;
+      sbuf->map.ranges[sbuf->map.num_ranges].start = start;
+      sbuf->map.ranges[sbuf->map.num_ranges].end = end;
+      ++sbuf->map.num_ranges;
+   } else {
+      /*
+       * Everything else failed, so just extend the nearest range.
+       *
+       * It is OK to do this because we always keep a local copy of the
+       * host buffer data, for SW TNL, and the host never modifies the buffer.
+       */
+
+      assert(nearest_range < SVGA_BUFFER_MAX_RANGES);
+      assert(nearest_range < sbuf->map.num_ranges);
+      sbuf->map.ranges[nearest_range].start = MIN2(sbuf->map.ranges[nearest_range].start, start);
+      sbuf->map.ranges[nearest_range].end   = MAX2(sbuf->map.ranges[nearest_range].end,   end);
+   }
 }
 
 
@@ -431,66 +411,30 @@ svga_buffer_map_range( struct pipe_screen *screen,
    struct svga_buffer *sbuf = svga_buffer( buf );
    void *map;
 
-   if(sbuf->swbuf) {
+   if (!sbuf->swbuf && !sbuf->hwbuf) {
+      if (svga_buffer_create_hw_storage(ss, sbuf) != PIPE_OK) {
+         /*
+          * We can't create a hardware buffer big enough, so create a malloc
+          * buffer instead.
+          */
+
+         debug_printf("%s: failed to allocate %u KB of DMA, splitting DMA transfers\n",
+                      __FUNCTION__,
+                      (sbuf->base.size + 1023)/1024);
+
+         sbuf->swbuf = align_malloc(sbuf->base.size, sbuf->base.alignment);
+      }
+   }
+
+   if (sbuf->swbuf) {
       /* User/malloc buffer */
       map = sbuf->swbuf;
    }
+   else if (sbuf->hwbuf) {
+      map = sws->buffer_map(sws, sbuf->hwbuf, usage);
+   }
    else {
-      if(!sbuf->hw.buf) {
-         struct svga_winsys_surface *handle = sbuf->handle;
-
-         if(svga_buffer_create_hw_storage(ss, sbuf) != PIPE_OK)
-            return NULL;
-         
-         /* Populate the hardware storage if the host surface pre-existed */
-         if((usage & PIPE_BUFFER_USAGE_CPU_READ) && handle) {
-            SVGA3dSurfaceDMAFlags flags;
-            enum pipe_error ret;
-            struct pipe_fence_handle *fence = NULL;
-            
-            SVGA_DBG(DEBUG_DMA|DEBUG_PERF, "dma from sid %p (buffer), bytes %u - %u\n", 
-                     sbuf->handle, 0, sbuf->base.size);
-
-            memset(&flags, 0, sizeof flags);
-            
-            ret = SVGA3D_BufferDMA(ss->swc,
-                                   sbuf->hw.buf,
-                                   sbuf->handle,
-                                   SVGA3D_READ_HOST_VRAM,
-                                   sbuf->base.size,
-                                   0,
-                                   flags);
-            if(ret != PIPE_OK) {
-               ss->swc->flush(ss->swc, NULL);
-               
-               ret = SVGA3D_BufferDMA(ss->swc,
-                                      sbuf->hw.buf,
-                                      sbuf->handle,
-                                      SVGA3D_READ_HOST_VRAM,
-                                      sbuf->base.size,
-                                      0,
-                                      flags);
-               assert(ret == PIPE_OK);
-            }
-            
-            ss->swc->flush(ss->swc, &fence);
-            sws->fence_finish(sws, fence, 0);
-            sws->fence_reference(sws, &fence, NULL);
-         }
-      }
-      else {
-         if((usage & PIPE_BUFFER_USAGE_CPU_READ) && !sbuf->needs_flush) {
-            /* We already had the hardware storage but we would have to issue
-             * a download if we hadn't, so move the buffer to the begginning
-             * of the LRU list.
-             */
-            assert(sbuf->head.prev && sbuf->head.next);
-            LIST_DEL(&sbuf->head);
-            LIST_ADD(&sbuf->head, &ss->cached_buffers);
-         }
-      }
-         
-      map = sws->buffer_map(sws, sbuf->hw.buf, usage);
+      map = NULL;
    }
 
    if(map) {
@@ -523,8 +467,7 @@ svga_buffer_flush_mapped_range( struct pipe_screen *screen,
    assert(sbuf->map.writing);
    if(sbuf->map.writing) {
       assert(sbuf->map.flush_explicit);
-      if(sbuf->hw.buf)
-         svga_buffer_upload_queue(sbuf, offset, offset + length);
+      svga_buffer_add_range(sbuf, offset, offset + length);
    }
    pipe_mutex_unlock(ss->swc_mutex);
 }
@@ -543,16 +486,15 @@ svga_buffer_unmap( struct pipe_screen *screen,
    if(sbuf->map.count)
       --sbuf->map.count;
 
-   if(sbuf->hw.buf)
-      sws->buffer_unmap(sws, sbuf->hw.buf);
+   if(sbuf->hwbuf)
+      sws->buffer_unmap(sws, sbuf->hwbuf);
 
    if(sbuf->map.writing) {
       if(!sbuf->map.flush_explicit) {
          /* No mapped range was flushed -- flush the whole buffer */
          SVGA_DBG(DEBUG_DMA, "flushing the whole buffer\n");
    
-         if(sbuf->hw.buf)
-            svga_buffer_upload_queue(sbuf, 0, sbuf->base.size);
+         svga_buffer_add_range(sbuf, 0, sbuf->base.size);
       }
       
       sbuf->map.writing = FALSE;
@@ -570,14 +512,15 @@ svga_buffer_destroy( struct pipe_buffer *buf )
 
    assert(!p_atomic_read(&buf->reference.count));
    
-   assert(!sbuf->needs_flush);
+   assert(!sbuf->dma.pending);
 
-   if(sbuf->handle) {
-      SVGA_DBG(DEBUG_DMA, "release sid %p sz %d\n", sbuf->handle, sbuf->base.size);
-      svga_screen_surface_destroy(ss, &sbuf->key, &sbuf->handle);
-   }
+   if(sbuf->handle)
+      svga_buffer_destroy_host_surface(ss, sbuf);
    
-   if(sbuf->hw.buf)
+   if(sbuf->uploaded.buffer)
+      pipe_buffer_reference(&sbuf->uploaded.buffer, NULL);
+
+   if(sbuf->hwbuf)
       svga_buffer_destroy_hw_storage(ss, sbuf);
    
    if(sbuf->swbuf && !sbuf->user)
@@ -595,6 +538,9 @@ svga_buffer_create(struct pipe_screen *screen,
    struct svga_screen *ss = svga_screen(screen);
    struct svga_buffer *sbuf;
    
+   assert(size);
+   assert(alignment);
+
    sbuf = CALLOC_STRUCT(svga_buffer);
    if(!sbuf)
       goto error1;
@@ -671,13 +617,14 @@ svga_screen_init_buffer_functions(struct pipe_screen *screen)
 }
 
 
-/** 
- * Copy the contents of the user buffer / malloc buffer to a hardware buffer.
+/**
+ * Copy the contents of the malloc buffer to a hardware buffer.
  */
 static INLINE enum pipe_error
 svga_buffer_update_hw(struct svga_screen *ss, struct svga_buffer *sbuf)
 {
-   if(!sbuf->hw.buf) {
+   assert(!sbuf->user);
+   if(!sbuf->hwbuf) {
       enum pipe_error ret;
       void *map;
       
@@ -686,20 +633,20 @@ svga_buffer_update_hw(struct svga_screen *ss, struct svga_buffer *sbuf)
          return PIPE_ERROR;
       
       ret = svga_buffer_create_hw_storage(ss, sbuf);
-      assert(ret == PIPE_OK);
       if(ret != PIPE_OK)
          return ret;
 
       pipe_mutex_lock(ss->swc_mutex);
-      map = ss->sws->buffer_map(ss->sws, sbuf->hw.buf, PIPE_BUFFER_USAGE_CPU_WRITE);
+      map = ss->sws->buffer_map(ss->sws, sbuf->hwbuf, PIPE_BUFFER_USAGE_CPU_WRITE);
       assert(map);
       if(!map) {
         pipe_mutex_unlock(ss->swc_mutex);
-         return PIPE_ERROR_OUT_OF_MEMORY;
+         svga_buffer_destroy_hw_storage(ss, sbuf);
+         return PIPE_ERROR;
       }
 
       memcpy(map, sbuf->swbuf, sbuf->base.size);
-      ss->sws->buffer_unmap(ss->sws, sbuf->hw.buf);
+      ss->sws->buffer_unmap(ss->sws, sbuf->hwbuf);
 
       /* This user/malloc buffer is now indistinguishable from a gpu buffer */
       assert(!sbuf->map.count);
@@ -711,10 +658,89 @@ svga_buffer_update_hw(struct svga_screen *ss, struct svga_buffer *sbuf)
          sbuf->swbuf = NULL;
       }
       
-      svga_buffer_upload_queue(sbuf, 0, sbuf->base.size);
+      pipe_mutex_unlock(ss->swc_mutex);
    }
    
-   pipe_mutex_unlock(ss->swc_mutex);
+   return PIPE_OK;
+}
+
+
+/**
+ * Upload the buffer to the host in a piecewise fashion.
+ *
+ * Used when the buffer is too big to fit in the GMR aperture.
+ */
+static INLINE enum pipe_error
+svga_buffer_upload_piecewise(struct svga_screen *ss,
+                             struct svga_context *svga,
+                             struct svga_buffer *sbuf)
+{
+   struct svga_winsys_screen *sws = ss->sws;
+   const unsigned alignment = sizeof(void *);
+   const unsigned usage = 0;
+   unsigned i;
+
+   assert(sbuf->map.num_ranges);
+   assert(!sbuf->dma.pending);
+
+   SVGA_DBG(DEBUG_DMA, "dma to sid %p\n", sbuf->handle);
+
+   for (i = 0; i < sbuf->map.num_ranges; ++i) {
+      struct svga_buffer_range *range = &sbuf->map.ranges[i];
+      unsigned offset = range->start;
+      unsigned size = range->end - range->start;
+
+      while (offset < range->end) {
+         struct svga_winsys_buffer *hwbuf;
+         uint8_t *map;
+         enum pipe_error ret;
+
+         if (offset + size > range->end)
+            size = range->end - offset;
+
+         hwbuf = svga_winsys_buffer_create(ss, alignment, usage, size);
+         while (!hwbuf) {
+            size /= 2;
+            if (!size)
+               return PIPE_ERROR_OUT_OF_MEMORY;
+            hwbuf = svga_winsys_buffer_create(ss, alignment, usage, size);
+         }
+
+         SVGA_DBG(DEBUG_DMA, "  bytes %u - %u\n",
+                  offset, offset + size);
+
+         map = sws->buffer_map(sws, hwbuf,
+                               PIPE_BUFFER_USAGE_CPU_WRITE |
+                               PIPE_BUFFER_USAGE_DISCARD);
+         assert(map);
+         if (map) {
+            memcpy(map, sbuf->swbuf, size);
+            sws->buffer_unmap(sws, hwbuf);
+         }
+
+         ret = SVGA3D_BufferDMA(svga->swc,
+                                hwbuf, sbuf->handle,
+                                SVGA3D_WRITE_HOST_VRAM,
+                                size, 0, offset, sbuf->dma.flags);
+         if(ret != PIPE_OK) {
+            svga_context_flush(svga, NULL);
+            ret =  SVGA3D_BufferDMA(svga->swc,
+                                    hwbuf, sbuf->handle,
+                                    SVGA3D_WRITE_HOST_VRAM,
+                                    size, 0, offset, sbuf->dma.flags);
+            assert(ret == PIPE_OK);
+         }
+
+         sbuf->dma.flags.discard = FALSE;
+
+         sws->buffer_destroy(sws, hwbuf);
+
+         offset += size;
+      }
+   }
+
+   sbuf->map.num_ranges = 0;
+
    return PIPE_OK;
 }
 
@@ -734,35 +760,74 @@ svga_buffer_handle(struct svga_context *svga,
    sbuf = svga_buffer(buf);
    
    assert(!sbuf->map.count);
+   assert(!sbuf->user);
    
    if(!sbuf->handle) {
       ret = svga_buffer_create_host_surface(ss, sbuf);
       if(ret != PIPE_OK)
         return NULL;
-
-      ret = svga_buffer_update_hw(ss, sbuf);
-      if(ret != PIPE_OK)
-        return NULL;
    }
 
-   if(!sbuf->needs_flush && sbuf->hw.num_ranges) {
-      /* Queue the buffer for flushing */
-      ret = svga_buffer_upload_command(svga, sbuf);
-      if(ret != PIPE_OK)
-         /* XXX: Should probably have a richer return value */
-         return NULL;
+   assert(sbuf->handle);
+
+   if (sbuf->map.num_ranges) {
+      if (!sbuf->dma.pending) {
+         /*
+          * No pending DMA upload yet, so insert a DMA upload command now.
+          */
+
+         /*
+          * Migrate the data from swbuf -> hwbuf if necessary.
+          */
+         ret = svga_buffer_update_hw(ss, sbuf);
+         if (ret == PIPE_OK) {
+            /*
+             * Queue a dma command.
+             */
 
-      assert(sbuf->hw.svga == svga);
+            ret = svga_buffer_upload_command(svga, sbuf);
+            if (ret == PIPE_ERROR_OUT_OF_MEMORY) {
+               svga_context_flush(svga, NULL);
+               ret = svga_buffer_upload_command(svga, sbuf);
+               assert(ret == PIPE_OK);
+            }
+            if (ret == PIPE_OK) {
+               sbuf->dma.pending = TRUE;
+               assert(!sbuf->head.prev && !sbuf->head.next);
+               LIST_ADDTAIL(&sbuf->head, &svga->dirty_buffers);
+            }
+         }
+         else if (ret == PIPE_ERROR_OUT_OF_MEMORY) {
+            /*
+             * The buffer is too big to fit in the GMR aperture, so break it in
+             * smaller pieces.
+             */
+            ret = svga_buffer_upload_piecewise(ss, svga, sbuf);
+         }
 
-      sbuf->needs_flush = TRUE;
-      assert(sbuf->head.prev && sbuf->head.next);
-      LIST_DEL(&sbuf->head);
-      LIST_ADDTAIL(&sbuf->head, &svga->dirty_buffers);
+         if (ret != PIPE_OK) {
+            /*
+             * Something unexpected happened above. There is very little that
+             * we can do other than proceeding while ignoring the dirty ranges.
+             */
+            assert(0);
+            sbuf->map.num_ranges = 0;
+         }
+      }
+      else {
+         /*
+          * There a pending dma already. Make sure it is from this context.
+          */
+         assert(sbuf->dma.svga == svga);
+      }
    }
 
+   assert(!sbuf->map.num_ranges || sbuf->dma.pending);
+
    return sbuf->handle;
 }
 
+
 struct pipe_buffer *
 svga_screen_buffer_wrap_surface(struct pipe_screen *screen,
                                enum SVGA3dSurfaceFormat format,
@@ -815,7 +880,7 @@ svga_context_flush_buffers(struct svga_context *svga)
       sbuf = LIST_ENTRY(struct svga_buffer, curr, head);
 
       assert(p_atomic_read(&sbuf->base.reference.count) != 0);
-      assert(sbuf->needs_flush);
+      assert(sbuf->dma.pending);
       
       svga_buffer_upload_flush(svga, sbuf);