d7df04c03c94c9dc4d75ff1b72b90aa017f95cf2
[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 remove_from_ht(struct hash_table *ht, void *key)
42 {
43 struct hash_entry *entry = _mesa_hash_table_search(ht, key);
44 _mesa_hash_table_remove(ht, entry);
45 }
46
47 static void
48 v3d_job_free(struct v3d_context *v3d, struct v3d_job *job)
49 {
50 struct set_entry *entry;
51
52 set_foreach(job->bos, entry) {
53 struct v3d_bo *bo = (struct v3d_bo *)entry->key;
54 v3d_bo_unreference(&bo);
55 }
56
57 remove_from_ht(v3d->jobs, &job->key);
58
59 if (job->write_prscs) {
60 struct set_entry *entry;
61
62 set_foreach(job->write_prscs, entry) {
63 const struct pipe_resource *prsc = entry->key;
64
65 remove_from_ht(v3d->write_jobs, (void *)prsc);
66 }
67 }
68
69 for (int i = 0; i < VC5_MAX_DRAW_BUFFERS; i++) {
70 if (job->cbufs[i]) {
71 remove_from_ht(v3d->write_jobs, job->cbufs[i]->texture);
72 pipe_surface_reference(&job->cbufs[i], NULL);
73 }
74 }
75 if (job->zsbuf) {
76 struct v3d_resource *rsc = v3d_resource(job->zsbuf->texture);
77 if (rsc->separate_stencil)
78 remove_from_ht(v3d->write_jobs,
79 &rsc->separate_stencil->base);
80
81 remove_from_ht(v3d->write_jobs, job->zsbuf->texture);
82 pipe_surface_reference(&job->zsbuf, NULL);
83 }
84
85 if (v3d->job == job)
86 v3d->job = NULL;
87
88 v3d_destroy_cl(&job->bcl);
89 v3d_destroy_cl(&job->rcl);
90 v3d_destroy_cl(&job->indirect);
91 v3d_bo_unreference(&job->tile_alloc);
92 v3d_bo_unreference(&job->tile_state);
93
94 ralloc_free(job);
95 }
96
97 static struct v3d_job *
98 v3d_job_create(struct v3d_context *v3d)
99 {
100 struct v3d_job *job = rzalloc(v3d, struct v3d_job);
101
102 job->v3d = v3d;
103
104 v3d_init_cl(job, &job->bcl);
105 v3d_init_cl(job, &job->rcl);
106 v3d_init_cl(job, &job->indirect);
107
108 job->draw_min_x = ~0;
109 job->draw_min_y = ~0;
110 job->draw_max_x = 0;
111 job->draw_max_y = 0;
112
113 job->bos = _mesa_set_create(job,
114 _mesa_hash_pointer,
115 _mesa_key_pointer_equal);
116 return job;
117 }
118
119 void
120 v3d_job_add_bo(struct v3d_job *job, struct v3d_bo *bo)
121 {
122 if (!bo)
123 return;
124
125 if (_mesa_set_search(job->bos, bo))
126 return;
127
128 v3d_bo_reference(bo);
129 _mesa_set_add(job->bos, bo);
130 job->referenced_size += bo->size;
131
132 uint32_t *bo_handles = (void *)(uintptr_t)job->submit.bo_handles;
133
134 if (job->submit.bo_handle_count >= job->bo_handles_size) {
135 job->bo_handles_size = MAX2(4, job->bo_handles_size * 2);
136 bo_handles = reralloc(job, bo_handles,
137 uint32_t, job->bo_handles_size);
138 job->submit.bo_handles = (uintptr_t)(void *)bo_handles;
139 }
140 bo_handles[job->submit.bo_handle_count++] = bo->handle;
141 }
142
143 void
144 v3d_job_add_write_resource(struct v3d_job *job, struct pipe_resource *prsc)
145 {
146 struct v3d_context *v3d = job->v3d;
147
148 if (!job->write_prscs) {
149 job->write_prscs = _mesa_set_create(job,
150 _mesa_hash_pointer,
151 _mesa_key_pointer_equal);
152 }
153
154 _mesa_set_add(job->write_prscs, prsc);
155 _mesa_hash_table_insert(v3d->write_jobs, prsc, job);
156 }
157
158 void
159 v3d_flush_jobs_writing_resource(struct v3d_context *v3d,
160 struct pipe_resource *prsc)
161 {
162 struct hash_entry *entry = _mesa_hash_table_search(v3d->write_jobs,
163 prsc);
164 if (entry) {
165 struct v3d_job *job = entry->data;
166 v3d_job_submit(v3d, job);
167 }
168 }
169
170 void
171 v3d_flush_jobs_reading_resource(struct v3d_context *v3d,
172 struct pipe_resource *prsc)
173 {
174 struct v3d_resource *rsc = v3d_resource(prsc);
175
176 v3d_flush_jobs_writing_resource(v3d, prsc);
177
178 struct hash_entry *entry;
179 hash_table_foreach(v3d->jobs, entry) {
180 struct v3d_job *job = entry->data;
181
182 if (_mesa_set_search(job->bos, rsc->bo)) {
183 v3d_job_submit(v3d, job);
184 /* Reminder: v3d->jobs is safe to keep iterating even
185 * after deletion of an entry.
186 */
187 continue;
188 }
189 }
190 }
191
192 static void
193 v3d_job_set_tile_buffer_size(struct v3d_job *job)
194 {
195 static const uint8_t tile_sizes[] = {
196 64, 64,
197 64, 32,
198 32, 32,
199 32, 16,
200 16, 16,
201 };
202 int tile_size_index = 0;
203 if (job->msaa)
204 tile_size_index += 2;
205
206 if (job->cbufs[3] || job->cbufs[2])
207 tile_size_index += 2;
208 else if (job->cbufs[1])
209 tile_size_index++;
210
211 int max_bpp = RENDER_TARGET_MAXIMUM_32BPP;
212 for (int i = 0; i < VC5_MAX_DRAW_BUFFERS; i++) {
213 if (job->cbufs[i]) {
214 struct v3d_surface *surf = v3d_surface(job->cbufs[i]);
215 max_bpp = MAX2(max_bpp, surf->internal_bpp);
216 }
217 }
218 job->internal_bpp = max_bpp;
219 STATIC_ASSERT(RENDER_TARGET_MAXIMUM_32BPP == 0);
220 tile_size_index += max_bpp;
221
222 assert(tile_size_index < ARRAY_SIZE(tile_sizes));
223 job->tile_width = tile_sizes[tile_size_index * 2 + 0];
224 job->tile_height = tile_sizes[tile_size_index * 2 + 1];
225 }
226
227 /**
228 * Returns a v3d_job struture for tracking V3D rendering to a particular FBO.
229 *
230 * If we've already started rendering to this FBO, then return old same job,
231 * otherwise make a new one. If we're beginning rendering to an FBO, make
232 * sure that any previous reads of the FBO (or writes to its color/Z surfaces)
233 * have been flushed.
234 */
235 struct v3d_job *
236 v3d_get_job(struct v3d_context *v3d,
237 struct pipe_surface **cbufs, struct pipe_surface *zsbuf)
238 {
239 /* Return the existing job for this FBO if we have one */
240 struct v3d_job_key local_key = {
241 .cbufs = {
242 cbufs[0],
243 cbufs[1],
244 cbufs[2],
245 cbufs[3],
246 },
247 .zsbuf = zsbuf,
248 };
249 struct hash_entry *entry = _mesa_hash_table_search(v3d->jobs,
250 &local_key);
251 if (entry)
252 return entry->data;
253
254 /* Creating a new job. Make sure that any previous jobs reading or
255 * writing these buffers are flushed.
256 */
257 struct v3d_job *job = v3d_job_create(v3d);
258
259 for (int i = 0; i < VC5_MAX_DRAW_BUFFERS; i++) {
260 if (cbufs[i]) {
261 v3d_flush_jobs_reading_resource(v3d, cbufs[i]->texture);
262 pipe_surface_reference(&job->cbufs[i], cbufs[i]);
263
264 if (cbufs[i]->texture->nr_samples > 1)
265 job->msaa = true;
266 }
267 }
268 if (zsbuf) {
269 v3d_flush_jobs_reading_resource(v3d, zsbuf->texture);
270 pipe_surface_reference(&job->zsbuf, zsbuf);
271 if (zsbuf->texture->nr_samples > 1)
272 job->msaa = true;
273 }
274
275 v3d_job_set_tile_buffer_size(job);
276
277 for (int i = 0; i < VC5_MAX_DRAW_BUFFERS; i++) {
278 if (cbufs[i])
279 _mesa_hash_table_insert(v3d->write_jobs,
280 cbufs[i]->texture, job);
281 }
282 if (zsbuf) {
283 _mesa_hash_table_insert(v3d->write_jobs, zsbuf->texture, job);
284
285 struct v3d_resource *rsc = v3d_resource(zsbuf->texture);
286 if (rsc->separate_stencil) {
287 v3d_flush_jobs_reading_resource(v3d,
288 &rsc->separate_stencil->base);
289 _mesa_hash_table_insert(v3d->write_jobs,
290 &rsc->separate_stencil->base,
291 job);
292 }
293 }
294
295 memcpy(&job->key, &local_key, sizeof(local_key));
296 _mesa_hash_table_insert(v3d->jobs, &job->key, job);
297
298 return job;
299 }
300
301 struct v3d_job *
302 v3d_get_job_for_fbo(struct v3d_context *v3d)
303 {
304 if (v3d->job)
305 return v3d->job;
306
307 struct pipe_surface **cbufs = v3d->framebuffer.cbufs;
308 struct pipe_surface *zsbuf = v3d->framebuffer.zsbuf;
309 struct v3d_job *job = v3d_get_job(v3d, cbufs, zsbuf);
310
311 /* The dirty flags are tracking what's been updated while v3d->job has
312 * been bound, so set them all to ~0 when switching between jobs. We
313 * also need to reset all state at the start of rendering.
314 */
315 v3d->dirty = ~0;
316
317 /* If we're binding to uninitialized buffers, no need to load their
318 * contents before drawing.
319 */
320 for (int i = 0; i < 4; i++) {
321 if (cbufs[i]) {
322 struct v3d_resource *rsc = v3d_resource(cbufs[i]->texture);
323 if (!rsc->writes)
324 job->clear |= PIPE_CLEAR_COLOR0 << i;
325 }
326 }
327
328 if (zsbuf) {
329 struct v3d_resource *rsc = v3d_resource(zsbuf->texture);
330 if (!rsc->writes)
331 job->clear |= PIPE_CLEAR_DEPTH | PIPE_CLEAR_STENCIL;
332 }
333
334 job->draw_tiles_x = DIV_ROUND_UP(v3d->framebuffer.width,
335 job->tile_width);
336 job->draw_tiles_y = DIV_ROUND_UP(v3d->framebuffer.height,
337 job->tile_height);
338
339 v3d->job = job;
340
341 return job;
342 }
343
344 static void
345 v3d_clif_dump(struct v3d_context *v3d, struct v3d_job *job)
346 {
347 if (!(V3D_DEBUG & (V3D_DEBUG_CL | V3D_DEBUG_CLIF)))
348 return;
349
350 struct clif_dump *clif = clif_dump_init(&v3d->screen->devinfo,
351 stderr,
352 V3D_DEBUG & V3D_DEBUG_CL);
353
354 struct set_entry *entry;
355 set_foreach(job->bos, entry) {
356 struct v3d_bo *bo = (void *)entry->key;
357 char *name = ralloc_asprintf(NULL, "%s_0x%x",
358 bo->name, bo->offset);
359
360 v3d_bo_map(bo);
361 clif_dump_add_bo(clif, name, bo->offset, bo->size, bo->map);
362
363 ralloc_free(name);
364 }
365
366 fprintf(stderr, "BCL: 0x%08x..0x%08x\n",
367 job->submit.bcl_start, job->submit.bcl_end);
368
369 clif_dump_add_cl(clif, job->submit.bcl_start, job->submit.bcl_end);
370
371 fprintf(stderr, "RCL: 0x%08x..0x%08x\n",
372 job->submit.rcl_start, job->submit.rcl_end);
373 clif_dump_add_cl(clif, job->submit.rcl_start, job->submit.rcl_end);
374
375 clif_dump(clif);
376
377 clif_dump_destroy(clif);
378 }
379
380 /**
381 * Submits the job to the kernel and then reinitializes it.
382 */
383 void
384 v3d_job_submit(struct v3d_context *v3d, struct v3d_job *job)
385 {
386 MAYBE_UNUSED struct v3d_screen *screen = v3d->screen;
387
388 if (!job->needs_flush)
389 goto done;
390
391 if (v3d->screen->devinfo.ver >= 41)
392 v3d41_emit_rcl(job);
393 else
394 v3d33_emit_rcl(job);
395
396 if (cl_offset(&job->bcl) > 0) {
397 if (screen->devinfo.ver >= 41)
398 v3d41_bcl_epilogue(v3d, job);
399 else
400 v3d33_bcl_epilogue(v3d, job);
401 }
402
403 job->submit.out_sync = v3d->out_sync;
404 job->submit.bcl_end = job->bcl.bo->offset + cl_offset(&job->bcl);
405 job->submit.rcl_end = job->rcl.bo->offset + cl_offset(&job->rcl);
406
407 /* On V3D 4.1, the tile alloc/state setup moved to register writes
408 * instead of binner packets.
409 */
410 if (screen->devinfo.ver >= 41) {
411 v3d_job_add_bo(job, job->tile_alloc);
412 job->submit.qma = job->tile_alloc->offset;
413 job->submit.qms = job->tile_alloc->size;
414
415 v3d_job_add_bo(job, job->tile_state);
416 job->submit.qts = job->tile_state->offset;
417 }
418
419 v3d_clif_dump(v3d, job);
420
421 if (!(V3D_DEBUG & V3D_DEBUG_NORAST)) {
422 int ret;
423
424 #ifndef USE_V3D_SIMULATOR
425 ret = drmIoctl(v3d->fd, DRM_IOCTL_V3D_SUBMIT_CL, &job->submit);
426 #else
427 ret = v3d_simulator_flush(v3d, &job->submit, job);
428 #endif
429 static bool warned = false;
430 if (ret && !warned) {
431 fprintf(stderr, "Draw call returned %s. "
432 "Expect corruption.\n", strerror(errno));
433 warned = true;
434 }
435 }
436
437 done:
438 v3d_job_free(v3d, job);
439 }
440
441 static bool
442 v3d_job_compare(const void *a, const void *b)
443 {
444 return memcmp(a, b, sizeof(struct v3d_job_key)) == 0;
445 }
446
447 static uint32_t
448 v3d_job_hash(const void *key)
449 {
450 return _mesa_hash_data(key, sizeof(struct v3d_job_key));
451 }
452
453 void
454 v3d_job_init(struct v3d_context *v3d)
455 {
456 v3d->jobs = _mesa_hash_table_create(v3d,
457 v3d_job_hash,
458 v3d_job_compare);
459 v3d->write_jobs = _mesa_hash_table_create(v3d,
460 _mesa_hash_pointer,
461 _mesa_key_pointer_equal);
462 }
463