nouveau_buffer.c revision 3464ebd5
1
2#include "util/u_inlines.h"
3#include "util/u_memory.h"
4#include "util/u_math.h"
5
6#include "nouveau_screen.h"
7#include "nouveau_context.h"
8#include "nouveau_winsys.h"
9#include "nouveau_fence.h"
10#include "nouveau_buffer.h"
11#include "nouveau_mm.h"
12
13struct nouveau_transfer {
14   struct pipe_transfer base;
15};
16
17static INLINE struct nouveau_transfer *
18nouveau_transfer(struct pipe_transfer *transfer)
19{
20   return (struct nouveau_transfer *)transfer;
21}
22
23static INLINE boolean
24nouveau_buffer_allocate(struct nouveau_screen *screen,
25                        struct nv04_resource *buf, unsigned domain)
26{
27   if (domain == NOUVEAU_BO_VRAM) {
28      buf->mm = nouveau_mm_allocate(screen->mm_VRAM, buf->base.width0,
29                                    &buf->bo, &buf->offset);
30      if (!buf->bo)
31         return nouveau_buffer_allocate(screen, buf, NOUVEAU_BO_GART);
32   } else
33   if (domain == NOUVEAU_BO_GART) {
34      buf->mm = nouveau_mm_allocate(screen->mm_GART, buf->base.width0,
35                                    &buf->bo, &buf->offset);
36      if (!buf->bo)
37         return FALSE;
38   }
39   if (domain != NOUVEAU_BO_GART) {
40      if (!buf->data) {
41         buf->data = MALLOC(buf->base.width0);
42         if (!buf->data)
43            return FALSE;
44      }
45   }
46   buf->domain = domain;
47   return TRUE;
48}
49
50static INLINE void
51release_allocation(struct nouveau_mm_allocation **mm,
52                   struct nouveau_fence *fence)
53{
54   nouveau_fence_work(fence, nouveau_mm_free_work, *mm);
55   (*mm) = NULL;
56}
57
58INLINE void
59nouveau_buffer_release_gpu_storage(struct nv04_resource *buf)
60{
61   nouveau_bo_ref(NULL, &buf->bo);
62
63   if (buf->mm)
64      release_allocation(&buf->mm, buf->fence);
65
66   buf->domain = 0;
67}
68
69static INLINE boolean
70nouveau_buffer_reallocate(struct nouveau_screen *screen,
71                          struct nv04_resource *buf, unsigned domain)
72{
73   nouveau_buffer_release_gpu_storage(buf);
74
75   return nouveau_buffer_allocate(screen, buf, domain);
76}
77
78static void
79nouveau_buffer_destroy(struct pipe_screen *pscreen,
80                       struct pipe_resource *presource)
81{
82   struct nv04_resource *res = nv04_resource(presource);
83
84   nouveau_buffer_release_gpu_storage(res);
85
86   if (res->data && !(res->status & NOUVEAU_BUFFER_STATUS_USER_MEMORY))
87      FREE(res->data);
88
89   FREE(res);
90}
91
92/* Maybe just migrate to GART right away if we actually need to do this. */
93boolean
94nouveau_buffer_download(struct nouveau_context *nv, struct nv04_resource *buf,
95                        unsigned start, unsigned size)
96{
97   struct nouveau_mm_allocation *mm;
98   struct nouveau_bo *bounce = NULL;
99   uint32_t offset;
100
101   assert(buf->domain == NOUVEAU_BO_VRAM);
102
103   mm = nouveau_mm_allocate(nv->screen->mm_GART, size, &bounce, &offset);
104   if (!bounce)
105      return FALSE;
106
107   nv->copy_data(nv, bounce, offset, NOUVEAU_BO_GART,
108                 buf->bo, buf->offset + start, NOUVEAU_BO_VRAM, size);
109
110   if (nouveau_bo_map_range(bounce, offset, size, NOUVEAU_BO_RD))
111      return FALSE;
112   memcpy(buf->data + start, bounce->map, size);
113   nouveau_bo_unmap(bounce);
114
115   buf->status &= ~NOUVEAU_BUFFER_STATUS_GPU_WRITING;
116
117   nouveau_bo_ref(NULL, &bounce);
118   if (mm)
119      nouveau_mm_free(mm);
120   return TRUE;
121}
122
123static boolean
124nouveau_buffer_upload(struct nouveau_context *nv, struct nv04_resource *buf,
125                      unsigned start, unsigned size)
126{
127   struct nouveau_mm_allocation *mm;
128   struct nouveau_bo *bounce = NULL;
129   uint32_t offset;
130
131   if (size <= 192) {
132      nv->push_data(nv, buf->bo, buf->offset + start, buf->domain,
133                    size, buf->data + start);
134      return TRUE;
135   }
136
137   mm = nouveau_mm_allocate(nv->screen->mm_GART, size, &bounce, &offset);
138   if (!bounce)
139      return FALSE;
140
141   nouveau_bo_map_range(bounce, offset, size,
142                        NOUVEAU_BO_WR | NOUVEAU_BO_NOSYNC);
143   memcpy(bounce->map, buf->data + start, size);
144   nouveau_bo_unmap(bounce);
145
146   nv->copy_data(nv, buf->bo, buf->offset + start, NOUVEAU_BO_VRAM,
147                 bounce, offset, NOUVEAU_BO_GART, size);
148
149   nouveau_bo_ref(NULL, &bounce);
150   if (mm)
151      release_allocation(&mm, nv->screen->fence.current);
152
153   if (start == 0 && size == buf->base.width0)
154      buf->status &= ~NOUVEAU_BUFFER_STATUS_GPU_WRITING;
155   return TRUE;
156}
157
158static struct pipe_transfer *
159nouveau_buffer_transfer_get(struct pipe_context *pipe,
160                            struct pipe_resource *resource,
161                            unsigned level, unsigned usage,
162                            const struct pipe_box *box)
163{
164   struct nv04_resource *buf = nv04_resource(resource);
165   struct nouveau_context *nv = nouveau_context(pipe);
166   struct nouveau_transfer *xfr = CALLOC_STRUCT(nouveau_transfer);
167   if (!xfr)
168      return NULL;
169
170   xfr->base.resource = resource;
171   xfr->base.box.x = box->x;
172   xfr->base.box.width = box->width;
173   xfr->base.usage = usage;
174
175   if (buf->domain == NOUVEAU_BO_VRAM) {
176      if (usage & PIPE_TRANSFER_READ) {
177         if (buf->status & NOUVEAU_BUFFER_STATUS_GPU_WRITING)
178            nouveau_buffer_download(nv, buf, 0, buf->base.width0);
179      }
180   }
181
182   return &xfr->base;
183}
184
185static void
186nouveau_buffer_transfer_destroy(struct pipe_context *pipe,
187                                struct pipe_transfer *transfer)
188{
189   struct nv04_resource *buf = nv04_resource(transfer->resource);
190   struct nouveau_transfer *xfr = nouveau_transfer(transfer);
191   struct nouveau_context *nv = nouveau_context(pipe);
192
193   if (xfr->base.usage & PIPE_TRANSFER_WRITE) {
194      /* writing is worse */
195      nouveau_buffer_adjust_score(nv, buf, -5000);
196
197      if (buf->domain == NOUVEAU_BO_VRAM) {
198         nouveau_buffer_upload(nv, buf, transfer->box.x, transfer->box.width);
199      }
200
201      if (buf->domain != 0 && (buf->base.bind & (PIPE_BIND_VERTEX_BUFFER |
202                                                 PIPE_BIND_INDEX_BUFFER)))
203         nouveau_context(pipe)->vbo_dirty = TRUE;
204   }
205
206   FREE(xfr);
207}
208
209static INLINE boolean
210nouveau_buffer_sync(struct nv04_resource *buf, unsigned rw)
211{
212   if (rw == PIPE_TRANSFER_READ) {
213      if (!buf->fence_wr)
214         return TRUE;
215      if (!nouveau_fence_wait(buf->fence_wr))
216         return FALSE;
217   } else {
218      if (!buf->fence)
219         return TRUE;
220      if (!nouveau_fence_wait(buf->fence))
221         return FALSE;
222
223      nouveau_fence_ref(NULL, &buf->fence);
224   }
225   nouveau_fence_ref(NULL, &buf->fence_wr);
226
227   return TRUE;
228}
229
230static INLINE boolean
231nouveau_buffer_busy(struct nv04_resource *buf, unsigned rw)
232{
233   if (rw == PIPE_TRANSFER_READ)
234      return (buf->fence_wr && !nouveau_fence_signalled(buf->fence_wr));
235   else
236      return (buf->fence && !nouveau_fence_signalled(buf->fence));
237}
238
239static void *
240nouveau_buffer_transfer_map(struct pipe_context *pipe,
241                            struct pipe_transfer *transfer)
242{
243   struct nouveau_transfer *xfr = nouveau_transfer(transfer);
244   struct nv04_resource *buf = nv04_resource(transfer->resource);
245   struct nouveau_bo *bo = buf->bo;
246   uint8_t *map;
247   int ret;
248   uint32_t offset = xfr->base.box.x;
249   uint32_t flags;
250
251   nouveau_buffer_adjust_score(nouveau_context(pipe), buf, -250);
252
253   if (buf->domain != NOUVEAU_BO_GART)
254      return buf->data + offset;
255
256   if (buf->mm)
257      flags = NOUVEAU_BO_NOSYNC | NOUVEAU_BO_RDWR;
258   else
259      flags = nouveau_screen_transfer_flags(xfr->base.usage);
260
261   offset += buf->offset;
262
263   ret = nouveau_bo_map_range(buf->bo, offset, xfr->base.box.width, flags);
264   if (ret)
265      return NULL;
266   map = bo->map;
267
268   /* Unmap right now. Since multiple buffers can share a single nouveau_bo,
269    * not doing so might make future maps fail or trigger "reloc while mapped"
270    * errors. For now, mappings to userspace are guaranteed to be persistent.
271    */
272   nouveau_bo_unmap(bo);
273
274   if (buf->mm) {
275      if (xfr->base.usage & PIPE_TRANSFER_DONTBLOCK) {
276         if (nouveau_buffer_busy(buf, xfr->base.usage & PIPE_TRANSFER_READ_WRITE))
277            return NULL;
278      } else
279      if (!(xfr->base.usage & PIPE_TRANSFER_UNSYNCHRONIZED)) {
280         nouveau_buffer_sync(buf, xfr->base.usage & PIPE_TRANSFER_READ_WRITE);
281      }
282   }
283   return map;
284}
285
286
287
288static void
289nouveau_buffer_transfer_flush_region(struct pipe_context *pipe,
290                                     struct pipe_transfer *transfer,
291                                     const struct pipe_box *box)
292{
293   struct nv04_resource *res = nv04_resource(transfer->resource);
294   struct nouveau_bo *bo = res->bo;
295   unsigned offset = res->offset + transfer->box.x + box->x;
296
297   /* not using non-snoop system memory yet, no need for cflush */
298   if (1)
299      return;
300
301   /* XXX: maybe need to upload for VRAM buffers here */
302
303   nouveau_screen_bo_map_flush_range(pipe->screen, bo, offset, box->width);
304}
305
306static void
307nouveau_buffer_transfer_unmap(struct pipe_context *pipe,
308                              struct pipe_transfer *transfer)
309{
310   /* we've called nouveau_bo_unmap right after map */
311}
312
313const struct u_resource_vtbl nouveau_buffer_vtbl =
314{
315   u_default_resource_get_handle,     /* get_handle */
316   nouveau_buffer_destroy,               /* resource_destroy */
317   nouveau_buffer_transfer_get,          /* get_transfer */
318   nouveau_buffer_transfer_destroy,      /* transfer_destroy */
319   nouveau_buffer_transfer_map,          /* transfer_map */
320   nouveau_buffer_transfer_flush_region, /* transfer_flush_region */
321   nouveau_buffer_transfer_unmap,        /* transfer_unmap */
322   u_default_transfer_inline_write    /* transfer_inline_write */
323};
324
325struct pipe_resource *
326nouveau_buffer_create(struct pipe_screen *pscreen,
327                      const struct pipe_resource *templ)
328{
329   struct nouveau_screen *screen = nouveau_screen(pscreen);
330   struct nv04_resource *buffer;
331   boolean ret;
332
333   buffer = CALLOC_STRUCT(nv04_resource);
334   if (!buffer)
335      return NULL;
336
337   buffer->base = *templ;
338   buffer->vtbl = &nouveau_buffer_vtbl;
339   pipe_reference_init(&buffer->base.reference, 1);
340   buffer->base.screen = pscreen;
341
342   if ((buffer->base.bind & screen->sysmem_bindings) == screen->sysmem_bindings)
343      ret = nouveau_buffer_allocate(screen, buffer, 0);
344   else
345      ret = nouveau_buffer_allocate(screen, buffer, NOUVEAU_BO_GART);
346
347   if (ret == FALSE)
348      goto fail;
349
350   return &buffer->base;
351
352fail:
353   FREE(buffer);
354   return NULL;
355}
356
357
358struct pipe_resource *
359nouveau_user_buffer_create(struct pipe_screen *pscreen, void *ptr,
360                           unsigned bytes, unsigned bind)
361{
362   struct nv04_resource *buffer;
363
364   buffer = CALLOC_STRUCT(nv04_resource);
365   if (!buffer)
366      return NULL;
367
368   pipe_reference_init(&buffer->base.reference, 1);
369   buffer->vtbl = &nouveau_buffer_vtbl;
370   buffer->base.screen = pscreen;
371   buffer->base.format = PIPE_FORMAT_R8_UNORM;
372   buffer->base.usage = PIPE_USAGE_IMMUTABLE;
373   buffer->base.bind = bind;
374   buffer->base.width0 = bytes;
375   buffer->base.height0 = 1;
376   buffer->base.depth0 = 1;
377
378   buffer->data = ptr;
379   buffer->status = NOUVEAU_BUFFER_STATUS_USER_MEMORY;
380
381   return &buffer->base;
382}
383
384/* Like download, but for GART buffers. Merge ? */
385static INLINE boolean
386nouveau_buffer_data_fetch(struct nv04_resource *buf, struct nouveau_bo *bo,
387                          unsigned offset, unsigned size)
388{
389   if (!buf->data) {
390      buf->data = MALLOC(size);
391      if (!buf->data)
392         return FALSE;
393   }
394   if (nouveau_bo_map_range(bo, offset, size, NOUVEAU_BO_RD))
395      return FALSE;
396   memcpy(buf->data, bo->map, size);
397   nouveau_bo_unmap(bo);
398
399   return TRUE;
400}
401
402/* Migrate a linear buffer (vertex, index, constants) USER -> GART -> VRAM. */
403boolean
404nouveau_buffer_migrate(struct nouveau_context *nv,
405                       struct nv04_resource *buf, const unsigned new_domain)
406{
407   struct nouveau_screen *screen = nv->screen;
408   struct nouveau_bo *bo;
409   const unsigned old_domain = buf->domain;
410   unsigned size = buf->base.width0;
411   unsigned offset;
412   int ret;
413
414   assert(new_domain != old_domain);
415
416   if (new_domain == NOUVEAU_BO_GART && old_domain == 0) {
417      if (!nouveau_buffer_allocate(screen, buf, new_domain))
418         return FALSE;
419      ret = nouveau_bo_map_range(buf->bo, buf->offset, size, NOUVEAU_BO_WR |
420                                 NOUVEAU_BO_NOSYNC);
421      if (ret)
422         return ret;
423      memcpy(buf->bo->map, buf->data, size);
424      nouveau_bo_unmap(buf->bo);
425      FREE(buf->data);
426   } else
427   if (old_domain != 0 && new_domain != 0) {
428      struct nouveau_mm_allocation *mm = buf->mm;
429
430      if (new_domain == NOUVEAU_BO_VRAM) {
431         /* keep a system memory copy of our data in case we hit a fallback */
432         if (!nouveau_buffer_data_fetch(buf, buf->bo, buf->offset, size))
433            return FALSE;
434         debug_printf("migrating %u KiB to VRAM\n", size / 1024);
435      }
436
437      offset = buf->offset;
438      bo = buf->bo;
439      buf->bo = NULL;
440      buf->mm = NULL;
441      nouveau_buffer_allocate(screen, buf, new_domain);
442
443      nv->copy_data(nv, buf->bo, buf->offset, new_domain,
444                    bo, offset, old_domain, buf->base.width0);
445
446      nouveau_bo_ref(NULL, &bo);
447      if (mm)
448         release_allocation(&mm, screen->fence.current);
449   } else
450   if (new_domain == NOUVEAU_BO_VRAM && old_domain == 0) {
451      if (!nouveau_buffer_allocate(screen, buf, NOUVEAU_BO_VRAM))
452         return FALSE;
453      if (!nouveau_buffer_upload(nv, buf, 0, buf->base.width0))
454         return FALSE;
455   } else
456      return FALSE;
457
458   assert(buf->domain == new_domain);
459   return TRUE;
460}
461
462/* Migrate data from glVertexAttribPointer(non-VBO) user buffers to GART.
463 * We'd like to only allocate @size bytes here, but then we'd have to rebase
464 * the vertex indices ...
465 */
466boolean
467nouveau_user_buffer_upload(struct nv04_resource *buf,
468                           unsigned base, unsigned size)
469{
470   struct nouveau_screen *screen = nouveau_screen(buf->base.screen);
471   int ret;
472
473   assert(buf->status & NOUVEAU_BUFFER_STATUS_USER_MEMORY);
474
475   buf->base.width0 = base + size;
476   if (!nouveau_buffer_reallocate(screen, buf, NOUVEAU_BO_GART))
477      return FALSE;
478
479   ret = nouveau_bo_map_range(buf->bo, buf->offset + base, size,
480                              NOUVEAU_BO_WR | NOUVEAU_BO_NOSYNC);
481   if (ret)
482      return FALSE;
483   memcpy(buf->bo->map, buf->data + base, size);
484   nouveau_bo_unmap(buf->bo);
485
486   return TRUE;
487}
488