diff --git a/dlls/wined3d/buffer.c b/dlls/wined3d/buffer.c
index fd3eb08..637020f 100644
--- a/dlls/wined3d/buffer.c
+++ b/dlls/wined3d/buffer.c
@@ -109,19 +109,21 @@ static void buffer_create_buffer_object(struct wined3d_buffer *This)
 
     This->buffer_object_size = This->resource.size;
     This->buffer_object_usage = gl_usage;
-    This->dirty_start = 0;
-    This->dirty_end = This->resource.size;
 
     if(This->flags & WINED3D_BUFFER_DOUBLEBUFFER)
     {
-        This->flags |= WINED3D_BUFFER_DIRTY;
+        This->maps[0].start = 0;
+        This->maps[0].len = This->resource.size;
+        This->modified_areas = 1;
+        This->flags |= WINED3D_BUFFER_FULLMAP;
     }
     else
     {
         HeapFree(GetProcessHeap(), 0, This->resource.heapMemory);
         This->resource.allocatedMemory = NULL;
         This->resource.heapMemory = NULL;
-        This->flags &= ~WINED3D_BUFFER_DIRTY;
+        This->modified_areas = 0;
+        This->flags &= ~WINED3D_BUFFER_FULLMAP;
     }
 
     return;
@@ -637,6 +639,7 @@ static ULONG STDMETHODCALLTYPE buffer_Release(IWineD3DBuffer *iface)
         buffer_UnLoad(iface);
         resource_cleanup((IWineD3DResource *)iface);
         This->resource.parent_ops->wined3d_object_destroyed(This->resource.parent);
+        HeapFree(GetProcessHeap(), 0, This->maps);
         HeapFree(GetProcessHeap(), 0, This);
     }
 
@@ -683,13 +686,16 @@ static void STDMETHODCALLTYPE buffer_PreLoad(IWineD3DBuffer *iface)
 {
     struct wined3d_buffer *This = (struct wined3d_buffer *)iface;
     IWineD3DDeviceImpl *device = This->resource.device;
-    UINT start = 0, end = 0, vertices;
+    UINT start = 0, end = 0, len = 0, vertices;
     struct wined3d_context *context;
     BOOL decl_changed = FALSE;
     unsigned int i, j;
     BYTE *data;
+    ULONG modified_areas = This->modified_areas;
 
     TRACE("iface %p\n", iface);
+    This->modified_areas = 0;
+    This->flags &= ~WINED3D_BUFFER_FULLMAP;
 
     context = context_acquire(device, NULL, CTXUSAGE_RESOURCELOAD);
 
@@ -715,7 +721,7 @@ static void STDMETHODCALLTYPE buffer_PreLoad(IWineD3DBuffer *iface)
         This->flags |= WINED3D_BUFFER_HASDESC;
     }
 
-    if (!decl_changed && !(This->flags & WINED3D_BUFFER_HASDESC && This->flags & WINED3D_BUFFER_DIRTY))
+    if (!decl_changed && !(This->flags & WINED3D_BUFFER_HASDESC && modified_areas))
     {
         context_release(context);
         return;
@@ -766,28 +772,11 @@ static void STDMETHODCALLTYPE buffer_PreLoad(IWineD3DBuffer *iface)
     {
         /* The declaration changed, reload the whole buffer */
         WARN("Reloading buffer because of decl change\n");
-        start = 0;
-        end = This->resource.size;
-    }
-    else
-    {
-        /* No decl change, but dirty data, reload the changed stuff */
-        if (This->conversion_shift)
-        {
-            if (This->dirty_start != 0 || This->dirty_end != 0)
-            {
-                FIXME("Implement partial buffer loading with shifted conversion\n");
-            }
-        }
-        start = This->dirty_start;
-        end = This->dirty_end;
+        This->maps[0].start = 0;
+        This->maps[0].len = This->resource.size;
+        modified_areas = 1;
     }
 
-    /* Mark the buffer clean */
-    This->flags &= ~WINED3D_BUFFER_DIRTY;
-    This->dirty_start = 0;
-    This->dirty_end = 0;
-
     if(This->buffer_type_hint == GL_ELEMENT_ARRAY_BUFFER_ARB)
     {
         IWineD3DDeviceImpl_MarkStateDirty(This->resource.device, STATE_INDEXBUFFER);
@@ -811,8 +800,14 @@ static void STDMETHODCALLTYPE buffer_PreLoad(IWineD3DBuffer *iface)
         ENTER_GL();
         GL_EXTCALL(glBindBufferARB(This->buffer_type_hint, This->buffer_object));
         checkGLcall("glBindBufferARB");
-        GL_EXTCALL(glBufferSubDataARB(This->buffer_type_hint, start, end-start, This->resource.allocatedMemory + start));
-        checkGLcall("glBufferSubDataARB");
+        while(modified_areas)
+        {
+            modified_areas--;
+            start = This->maps[modified_areas].start;
+            len = This->maps[modified_areas].len;
+            GL_EXTCALL(glBufferSubDataARB(This->buffer_type_hint, start, len, This->resource.allocatedMemory + start));
+            checkGLcall("glBufferSubDataARB");
+        }
         LEAVE_GL();
 
         context_release(context);
@@ -832,6 +827,16 @@ static void STDMETHODCALLTYPE buffer_PreLoad(IWineD3DBuffer *iface)
         TRACE("Shifted conversion\n");
         data = HeapAlloc(GetProcessHeap(), 0, vertices * This->conversion_stride);
 
+        start = 0;
+        len = This->resource.size;
+        end = start + len;
+
+        if (This->modified_areas > 1
+           || This->maps[0].start || This->maps[0].len != This->resource.size)
+        {
+            FIXME("Implement partial buffer load with shifted conversion\n");
+        }
+
         for (i = start / This->stride; i < min((end / This->stride) + 1, vertices); ++i)
         {
             for (j = 0; j < This->stride; ++j)
@@ -871,41 +876,50 @@ static void STDMETHODCALLTYPE buffer_PreLoad(IWineD3DBuffer *iface)
     else
     {
         data = HeapAlloc(GetProcessHeap(), 0, This->resource.size);
-        memcpy(data + start, This->resource.allocatedMemory + start, end - start);
-        for (i = start / This->stride; i < min((end / This->stride) + 1, vertices); ++i)
+
+        while(modified_areas)
         {
-            for (j = 0; j < This->stride; ++j)
+            modified_areas--;
+            start = This->maps[modified_areas].start;
+            len = This->maps[modified_areas].len;
+            end = start + len;
+            
+            memcpy(data + start, This->resource.allocatedMemory + start, end - start);
+            for (i = start / This->stride; i < min((end / This->stride) + 1, vertices); ++i)
             {
-                switch(This->conversion_map[j])
+                for (j = 0; j < This->stride; ++j)
                 {
-                    case CONV_NONE:
-                        /* Done already */
-                        j += 3;
-                        break;
-                    case CONV_D3DCOLOR:
-                        fixup_d3dcolor((DWORD *) (data + i * This->stride + j));
-                        j += 3;
-                        break;
-
-                    case CONV_POSITIONT:
-                        fixup_transformed_pos((float *) (data + i * This->stride + j));
-                        j += 15;
-                        break;
-
-                    case CONV_FLOAT16_2:
-                        ERR("Did not expect FLOAT16 conversion in unshifted conversion\n");
-                    default:
-                        FIXME("Unimplemented conversion %d in shifted conversion\n", This->conversion_map[j]);
+                    switch(This->conversion_map[j])
+                    {
+                        case CONV_NONE:
+                            /* Done already */
+                            j += 3;
+                            break;
+                        case CONV_D3DCOLOR:
+                            fixup_d3dcolor((DWORD *) (data + i * This->stride + j));
+                            j += 3;
+                            break;
+
+                        case CONV_POSITIONT:
+                            fixup_transformed_pos((float *) (data + i * This->stride + j));
+                            j += 15;
+                            break;
+
+                        case CONV_FLOAT16_2:
+                            ERR("Did not expect FLOAT16 conversion in unshifted conversion\n");
+                        default:
+                            FIXME("Unimplemented conversion %d in shifted conversion\n", This->conversion_map[j]);
+                    }
                 }
             }
-        }
 
-        ENTER_GL();
-        GL_EXTCALL(glBindBufferARB(This->buffer_type_hint, This->buffer_object));
-        checkGLcall("glBindBufferARB");
-        GL_EXTCALL(glBufferSubDataARB(This->buffer_type_hint, start, end - start, data + start));
-        checkGLcall("glBufferSubDataARB");
-        LEAVE_GL();
+            ENTER_GL();
+            GL_EXTCALL(glBindBufferARB(This->buffer_type_hint, This->buffer_object));
+            checkGLcall("glBindBufferARB");
+            GL_EXTCALL(glBufferSubDataARB(This->buffer_type_hint, start, len, data + start));
+            checkGLcall("glBufferSubDataARB");
+            LEAVE_GL();
+        }
     }
 
     HeapFree(GetProcessHeap(), 0, data);
@@ -928,24 +942,38 @@ static HRESULT STDMETHODCALLTYPE buffer_Map(IWineD3DBuffer *iface, UINT offset,
 
     count = InterlockedIncrement(&This->lock_count);
 
-    if (This->flags & WINED3D_BUFFER_DIRTY)
+    if (This->maps_size <= This->modified_areas)
     {
-        if (This->dirty_start > offset) This->dirty_start = offset;
-
-        if (size)
+        void *new = HeapReAlloc(GetProcessHeap(), 0, This->maps,
+                                This->maps_size * 2 * sizeof(*This->maps));
+        if (!new)
         {
-            if (This->dirty_end < offset + size) This->dirty_end = offset + size;
+            ERR("Out of memory\n");
+            InterlockedDecrement(&This->lock_count);
+            return E_OUTOFMEMORY;
         }
         else
         {
-            This->dirty_end = This->resource.size;
+            This->maps = new;
+            This->maps_size *= 2;
+        }
+    }
+
+    if (size && (offset != 0 || size != This->resource.size))
+    {
+        if (!(This->flags & WINED3D_BUFFER_FULLMAP))
+        {
+            This->maps[This->modified_areas].start = offset;
+            This->maps[This->modified_areas].len = size;
+            This->modified_areas++;
         }
     }
     else
     {
-        This->dirty_start = offset;
-        if (size) This->dirty_end = offset + size;
-        else This->dirty_end = This->resource.size;
+        This->maps[0].start = 0;
+        This->maps[0].len = This->resource.size;
+        This->modified_areas = 1;
+        This->flags |= WINED3D_BUFFER_FULLMAP;
     }
 
     if(!(This->flags & WINED3D_BUFFER_DOUBLEBUFFER) && This->buffer_object)
@@ -968,10 +996,6 @@ static HRESULT STDMETHODCALLTYPE buffer_Map(IWineD3DBuffer *iface, UINT offset,
             context_release(context);
         }
     }
-    else
-    {
-        This->flags |= WINED3D_BUFFER_DIRTY;
-    }
 
     *data = This->resource.allocatedMemory + offset;
 
@@ -1022,6 +1046,9 @@ static HRESULT STDMETHODCALLTYPE buffer_Unmap(IWineD3DBuffer *iface)
         context_release(context);
 
         This->resource.allocatedMemory = NULL;
+        
+        This->modified_areas = 0;
+        This->flags &= ~WINED3D_BUFFER_FULLMAP;
     }
     else if (This->flags & WINED3D_BUFFER_HASDESC)
     {
@@ -1158,5 +1185,18 @@ HRESULT buffer_init(struct wined3d_buffer *buffer, IWineD3DDeviceImpl *device,
         }
     }
 
+    buffer->maps = HeapAlloc(GetProcessHeap(), 0, sizeof(*buffer->maps));
+    if (!buffer->maps)
+    {
+        /* Continuing would work, but the performance of plain vbos for dynamic
+         * buffers is usually worse than no vbo at all.
+         */
+        ERR("Out of memory\n");
+        buffer_UnLoad((IWineD3DBuffer *)buffer);
+        resource_cleanup((IWineD3DResource *)buffer);
+        return E_OUTOFMEMORY;
+    }
+    buffer->maps_size = 1;
+    
     return WINED3D_OK;
 }
diff --git a/dlls/wined3d/wined3d_private.h b/dlls/wined3d/wined3d_private.h
index 0883221..eb376fe 100644
--- a/dlls/wined3d/wined3d_private.h
+++ b/dlls/wined3d/wined3d_private.h
@@ -2344,8 +2344,14 @@ enum wined3d_buffer_conversion_type
     CONV_FLOAT16_2, /* Also handles FLOAT16_4 */
 };
 
+struct wined3d_map_range
+{
+    UINT start;
+    UINT len;
+};
+
 #define WINED3D_BUFFER_OPTIMIZED    0x01    /* Optimize has been called for the buffer */
-#define WINED3D_BUFFER_DIRTY        0x02    /* Buffer data has been modified */
+#define WINED3D_BUFFER_FULLMAP      0x02    /* The whole buffer has been mapped since the last load */
 #define WINED3D_BUFFER_HASDESC      0x04    /* A vertex description has been found */
 #define WINED3D_BUFFER_CREATEBO     0x08    /* Attempt to create a buffer object next PreLoad */
 #define WINED3D_BUFFER_DOUBLEBUFFER 0x10    /* Use a vbo and local allocated memory */
@@ -2364,9 +2370,9 @@ struct wined3d_buffer
     LONG bind_count;
     DWORD flags;
 
-    UINT dirty_start;
-    UINT dirty_end;
     LONG lock_count;
+    struct wined3d_map_range *maps;
+    ULONG maps_size, modified_areas;
 
     /* conversion stuff */
     UINT conversion_count;
