v3d: add a helper function to flush jobs using a BO
[mesa.git] / src / gallium / drivers / v3d / v3d_job.c
1 /*
2 * Copyright © 2014-2017 Broadcom
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a
5 * copy of this software and associated documentation files (the "Software"),
6 * to deal in the Software without restriction, including without limitation
7 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 * and/or sell copies of the Software, and to permit persons to whom the
9 * Software is furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice (including the next
12 * paragraph) shall be included in all copies or substantial portions of the
13 * Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21 * IN THE SOFTWARE.
22 */
23
24 /** @file v3d_job.c
25 *
26 * Functions for submitting VC5 render jobs to the kernel.
27 */
28
29 #include <xf86drm.h>
30 #include "v3d_context.h"
31 /* The OQ/semaphore packets are the same across V3D versions. */
32 #define V3D_VERSION 33
33 #include "broadcom/cle/v3dx_pack.h"
34 #include "broadcom/common/v3d_macros.h"
35 #include "util/hash_table.h"
36 #include "util/ralloc.h"
37 #include "util/set.h"
38 #include "broadcom/clif/clif_dump.h"
39
40 static void
41 v3d_job_free(struct v3d_context *v3d, struct v3d_job *job)
42 {
43 set_foreach(job->bos, entry) {
44 struct v3d_bo *bo = (struct v3d_bo *)entry->key;
45 v3d_bo_unreference(&bo);
46 }
47
48 _mesa_hash_table_remove_key(v3d->jobs, &job->key);
49
50 if (job->write_prscs) {
51 set_foreach(job->write_prscs, entry) {
52 const struct pipe_resource *prsc = entry->key;
53
54 _mesa_hash_table_remove_key(v3d->write_jobs, prsc);
55 }
56 }
57
58 for (int i = 0; i < V3D_MAX_DRAW_BUFFERS; i++) {
59 if (job->cbufs[i]) {
60 _mesa_hash_table_remove_key(v3d->write_jobs,
61 job->cbufs[i]->texture);
62 pipe_surface_reference(&job->cbufs[i], NULL);
63 }
64 }
65 if (job->zsbuf) {
66 struct v3d_resource *rsc = v3d_resource(job->zsbuf->texture);
67 if (rsc->separate_stencil)
68 _mesa_hash_table_remove_key(v3d->write_jobs,
69 &rsc->separate_stencil->base);
70
71 _mesa_hash_table_remove_key(v3d->write_jobs,
72 job->zsbuf->texture);
73 pipe_surface_reference(&job->zsbuf, NULL);
74 }
75
76 if (v3d->job == job)
77 v3d->job = NULL;
78
79 v3d_destroy_cl(&job->bcl);
80 v3d_destroy_cl(&job->rcl);
81 v3d_destroy_cl(&job->indirect);
82 v3d_bo_unreference(&job->tile_alloc);
83 v3d_bo_unreference(&job->tile_state);
84
85 ralloc_free(job);
86 }
87
88 static struct v3d_job *
89 v3d_job_create(struct v3d_context *v3d)
90 {
91 struct v3d_job *job = rzalloc(v3d, struct v3d_job);
92
93 job->v3d = v3d;
94
95 v3d_init_cl(job, &job->bcl);
96 v3d_init_cl(job, &job->rcl);
97 v3d_init_cl(job, &job->indirect);
98
99 job->draw_min_x = ~0;
100 job->draw_min_y = ~0;
101 job->draw_max_x = 0;
102 job->draw_max_y = 0;
103
104 job->bos = _mesa_set_create(job,
105 _mesa_hash_pointer,
106 _mesa_key_pointer_equal);
107 return job;
108 }
109
110 void
111 v3d_job_add_bo(struct v3d_job *job, struct v3d_bo *bo)
112 {
113 if (!bo)
114 return;
115
116 if (_mesa_set_search(job->bos, bo))
117 return;
118
119 v3d_bo_reference(bo);
120 _mesa_set_add(job->bos, bo);
121 job->referenced_size += bo->size;
122
123 uint32_t *bo_handles = (void *)(uintptr_t)job->submit.bo_handles;
124
125 if (job->submit.bo_handle_count >= job->bo_handles_size) {
126 job->bo_handles_size = MAX2(4, job->bo_handles_size * 2);
127 bo_handles = reralloc(job, bo_handles,
128 uint32_t, job->bo_handles_size);
129 job->submit.bo_handles = (uintptr_t)(void *)bo_handles;
130 }
131 bo_handles[job->submit.bo_handle_count++] = bo->handle;
132 }
133
134 void
135 v3d_job_add_write_resource(struct v3d_job *job, struct pipe_resource *prsc)
136 {
137 struct v3d_context *v3d = job->v3d;
138
139 if (!job->write_prscs) {
140 job->write_prscs = _mesa_set_create(job,
141 _mesa_hash_pointer,
142 _mesa_key_pointer_equal);
143 }
144
145 _mesa_set_add(job->write_prscs, prsc);
146 _mesa_hash_table_insert(v3d->write_jobs, prsc, job);
147 }
148
149 void
150 v3d_flush_jobs_using_bo(struct v3d_context *v3d, struct v3d_bo *bo)
151 {
152 hash_table_foreach(v3d->jobs, entry) {
153 struct v3d_job *job = entry->data;
154
155 if (_mesa_set_search(job->bos, bo))
156 v3d_job_submit(v3d, job);
157 }
158 }
159
160 void
161 v3d_flush_jobs_writing_resource(struct v3d_context *v3d,
162 struct pipe_resource *prsc)
163 {
164 struct hash_entry *entry = _mesa_hash_table_search(v3d->write_jobs,
165 prsc);
166 if (entry) {
167 struct v3d_job *job = entry->data;
168 v3d_job_submit(v3d, job);
169 }
170 }
171
172 void
173 v3d_flush_jobs_reading_resource(struct v3d_context *v3d,
174 struct pipe_resource *prsc)
175 {
176 struct v3d_resource *rsc = v3d_resource(prsc);
177
178 v3d_flush_jobs_writing_resource(v3d, prsc);
179
180 hash_table_foreach(v3d->jobs, entry) {
181 struct v3d_job *job = entry->data;
182
183 if (_mesa_set_search(job->bos, rsc->bo)) {
184 v3d_job_submit(v3d, job);
185 /* Reminder: v3d->jobs is safe to keep iterating even
186 * after deletion of an entry.
187 */
188 continue;
189 }
190 }
191 }
192
193 static void
194 v3d_job_set_tile_buffer_size(struct v3d_job *job)
195 {
196 static const uint8_t tile_sizes[] = {
197 64, 64,
198 64, 32,
199 32, 32,
200 32, 16,
201 16, 16,
202 };
203 int tile_size_index = 0;
204 if (job->msaa)
205 tile_size_index += 2;
206
207 if (job->cbufs[3] || job->cbufs[2])
208 tile_size_index += 2;
209 else if (job->cbufs[1])
210 tile_size_index++;
211
212 int max_bpp = RENDER_TARGET_MAXIMUM_32BPP;
213 for (int i = 0; i < V3D_MAX_DRAW_BUFFERS; i++) {
214 if (job->cbufs[i]) {
215 struct v3d_surface *surf = v3d_surface(job->cbufs[i]);
216 max_bpp = MAX2(max_bpp, surf->internal_bpp);
217 }
218 }
219 job->internal_bpp = max_bpp;
220 STATIC_ASSERT(RENDER_TARGET_MAXIMUM_32BPP == 0);
221 tile_size_index += max_bpp;
222
223 assert(tile_size_index < ARRAY_SIZE(tile_sizes));
224 job->tile_width = tile_sizes[tile_size_index * 2 + 0];
225 job->tile_height = tile_sizes[tile_size_index * 2 + 1];
226 }
227
228 /**
229 * Returns a v3d_job struture for tracking V3D rendering to a particular FBO.
230 *
231 * If we've already started rendering to this FBO, then return the same job,
232 * otherwise make a new one. If we're beginning rendering to an FBO, make
233 * sure that any previous reads of the FBO (or writes to its color/Z surfaces)
234 * have been flushed.
235 */
236 struct v3d_job *
237 v3d_get_job(struct v3d_context *v3d,
238 struct pipe_surface **cbufs, struct pipe_surface *zsbuf)
239 {
240 /* Return the existing job for this FBO if we have one */
241 struct v3d_job_key local_key = {
242 .cbufs = {
243 cbufs[0],
244 cbufs[1],
245 cbufs[2],
246 cbufs[3],
247 },
248 .zsbuf = zsbuf,
249 };
250 struct hash_entry *entry = _mesa_hash_table_search(v3d->jobs,
251 &local_key);
252 if (entry)
253 return entry->data;
254
255 /* Creating a new job. Make sure that any previous jobs reading or
256 * writing these buffers are flushed.
257 */
258 struct v3d_job *job = v3d_job_create(v3d);
259
260 for (int i = 0; i < V3D_MAX_DRAW_BUFFERS; i++) {
261 if (cbufs[i]) {
262 v3d_flush_jobs_reading_resource(v3d, cbufs[i]->texture);
263 pipe_surface_reference(&job->cbufs[i], cbufs[i]);
264
265 if (cbufs[i]->texture->nr_samples > 1)
266 job->msaa = true;
267 }
268 }
269 if (zsbuf) {
270 v3d_flush_jobs_reading_resource(v3d, zsbuf->texture);
271 pipe_surface_reference(&job->zsbuf, zsbuf);
272 if (zsbuf->texture->nr_samples > 1)
273 job->msaa = true;
274 }
275
276 for (int i = 0; i < V3D_MAX_DRAW_BUFFERS; i++) {
277 if (cbufs[i])
278 _mesa_hash_table_insert(v3d->write_jobs,
279 cbufs[i]->texture, job);
280 }
281 if (zsbuf) {
282 _mesa_hash_table_insert(v3d->write_jobs, zsbuf->texture, job);
283
284 struct v3d_resource *rsc = v3d_resource(zsbuf->texture);
285 if (rsc->separate_stencil) {
286 v3d_flush_jobs_reading_resource(v3d,
287 &rsc->separate_stencil->base);
288 _mesa_hash_table_insert(v3d->write_jobs,
289 &rsc->separate_stencil->base,
290 job);
291 }
292 }
293
294 memcpy(&job->key, &local_key, sizeof(local_key));
295 _mesa_hash_table_insert(v3d->jobs, &job->key, job);
296
297 return job;
298 }
299
300 struct v3d_job *
301 v3d_get_job_for_fbo(struct v3d_context *v3d)
302 {
303 if (v3d->job)
304 return v3d->job;
305
306 struct pipe_surface **cbufs = v3d->framebuffer.cbufs;
307 struct pipe_surface *zsbuf = v3d->framebuffer.zsbuf;
308 struct v3d_job *job = v3d_get_job(v3d, cbufs, zsbuf);
309
310 if (v3d->framebuffer.samples >= 1)
311 job->msaa = true;
312
313 v3d_job_set_tile_buffer_size(job);
314
315 /* The dirty flags are tracking what's been updated while v3d->job has
316 * been bound, so set them all to ~0 when switching between jobs. We
317 * also need to reset all state at the start of rendering.
318 */
319 v3d->dirty = ~0;
320
321 /* If we're binding to uninitialized buffers, no need to load their
322 * contents before drawing.
323 */
324 for (int i = 0; i < 4; i++) {
325 if (cbufs[i]) {
326 struct v3d_resource *rsc = v3d_resource(cbufs[i]->texture);
327 if (!rsc->writes)
328 job->clear |= PIPE_CLEAR_COLOR0 << i;
329 }
330 }
331
332 if (zsbuf) {
333 struct v3d_resource *rsc = v3d_resource(zsbuf->texture);
334 if (!rsc->writes)
335 job->clear |= PIPE_CLEAR_DEPTH | PIPE_CLEAR_STENCIL;
336 }
337
338 job->draw_tiles_x = DIV_ROUND_UP(v3d->framebuffer.width,
339 job->tile_width);
340 job->draw_tiles_y = DIV_ROUND_UP(v3d->framebuffer.height,
341 job->tile_height);
342
343 v3d->job = job;
344
345 return job;
346 }
347
348 static void
349 v3d_clif_dump(struct v3d_context *v3d, struct v3d_job *job)
350 {
351 if (!(V3D_DEBUG & (V3D_DEBUG_CL | V3D_DEBUG_CLIF)))
352 return;
353
354 struct clif_dump *clif = clif_dump_init(&v3d->screen->devinfo,
355 stderr,
356 V3D_DEBUG & V3D_DEBUG_CL);
357
358 set_foreach(job->bos, entry) {
359 struct v3d_bo *bo = (void *)entry->key;
360 char *name = ralloc_asprintf(NULL, "%s_0x%x",
361 bo->name, bo->offset);
362
363 v3d_bo_map(bo);
364 clif_dump_add_bo(clif, name, bo->offset, bo->size, bo->map);
365
366 ralloc_free(name);
367 }
368
369 clif_dump(clif, &job->submit);
370
371 clif_dump_destroy(clif);
372 }
373
374 /**
375 * Submits the job to the kernel and then reinitializes it.
376 */
377 void
378 v3d_job_submit(struct v3d_context *v3d, struct v3d_job *job)
379 {
380 MAYBE_UNUSED struct v3d_screen *screen = v3d->screen;
381
382 if (!job->needs_flush)
383 goto done;
384
385 if (v3d->screen->devinfo.ver >= 41)
386 v3d41_emit_rcl(job);
387 else
388 v3d33_emit_rcl(job);
389
390 if (cl_offset(&job->bcl) > 0) {
391 if (screen->devinfo.ver >= 41)
392 v3d41_bcl_epilogue(v3d, job);
393 else
394 v3d33_bcl_epilogue(v3d, job);
395 }
396
397 /* While the RCL will implicitly depend on the last RCL to have
398 * finished, we also need to block on any previous TFU job we may have
399 * dispatched.
400 */
401 job->submit.in_sync_rcl = v3d->out_sync;
402
403 /* Update the sync object for the last rendering by our context. */
404 job->submit.out_sync = v3d->out_sync;
405
406 job->submit.bcl_end = job->bcl.bo->offset + cl_offset(&job->bcl);
407 job->submit.rcl_end = job->rcl.bo->offset + cl_offset(&job->rcl);
408
409 /* On V3D 4.1, the tile alloc/state setup moved to register writes
410 * instead of binner packets.
411 */
412 if (screen->devinfo.ver >= 41) {
413 v3d_job_add_bo(job, job->tile_alloc);
414 job->submit.qma = job->tile_alloc->offset;
415 job->submit.qms = job->tile_alloc->size;
416
417 v3d_job_add_bo(job, job->tile_state);
418 job->submit.qts = job->tile_state->offset;
419 }
420
421 v3d_clif_dump(v3d, job);
422
423 if (!(V3D_DEBUG & V3D_DEBUG_NORAST)) {
424 int ret;
425
426 ret = v3d_ioctl(v3d->fd, DRM_IOCTL_V3D_SUBMIT_CL, &job->submit);
427 static bool warned = false;
428 if (ret && !warned) {
429 fprintf(stderr, "Draw call returned %s. "
430 "Expect corruption.\n", strerror(errno));
431 warned = true;
432 }
433 }
434
435 done:
436 v3d_job_free(v3d, job);
437 }
438
439 static bool
440 v3d_job_compare(const void *a, const void *b)
441 {
442 return memcmp(a, b, sizeof(struct v3d_job_key)) == 0;
443 }
444
445 static uint32_t
446 v3d_job_hash(const void *key)
447 {
448 return _mesa_hash_data(key, sizeof(struct v3d_job_key));
449 }
450
451 void
452 v3d_job_init(struct v3d_context *v3d)
453 {
454 v3d->jobs = _mesa_hash_table_create(v3d,
455 v3d_job_hash,
456 v3d_job_compare);
457 v3d->write_jobs = _mesa_hash_table_create(v3d,
458 _mesa_hash_pointer,
459 _mesa_key_pointer_equal);
460 }
461