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
40static void
41v3d_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
88static struct v3d_job *
89v3d_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
110void
111v3d_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
134void
135v3d_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
149void
150v3d_flush_jobs_writing_resource(struct v3d_context *v3d,
151                                struct pipe_resource *prsc)
152{
153        struct hash_entry *entry = _mesa_hash_table_search(v3d->write_jobs,
154                                                           prsc);
155        if (entry) {
156                struct v3d_job *job = entry->data;
157                v3d_job_submit(v3d, job);
158        }
159}
160
161void
162v3d_flush_jobs_reading_resource(struct v3d_context *v3d,
163                                struct pipe_resource *prsc)
164{
165        struct v3d_resource *rsc = v3d_resource(prsc);
166
167        v3d_flush_jobs_writing_resource(v3d, prsc);
168
169        hash_table_foreach(v3d->jobs, entry) {
170                struct v3d_job *job = entry->data;
171
172                if (_mesa_set_search(job->bos, rsc->bo)) {
173                        v3d_job_submit(v3d, job);
174                        /* Reminder: v3d->jobs is safe to keep iterating even
175                         * after deletion of an entry.
176                         */
177                        continue;
178                }
179        }
180}
181
182static void
183v3d_job_set_tile_buffer_size(struct v3d_job *job)
184{
185        static const uint8_t tile_sizes[] = {
186                64, 64,
187                64, 32,
188                32, 32,
189                32, 16,
190                16, 16,
191        };
192        int tile_size_index = 0;
193        if (job->msaa)
194                tile_size_index += 2;
195
196        if (job->cbufs[3] || job->cbufs[2])
197                tile_size_index += 2;
198        else if (job->cbufs[1])
199                tile_size_index++;
200
201        int max_bpp = RENDER_TARGET_MAXIMUM_32BPP;
202        for (int i = 0; i < V3D_MAX_DRAW_BUFFERS; i++) {
203                if (job->cbufs[i]) {
204                        struct v3d_surface *surf = v3d_surface(job->cbufs[i]);
205                        max_bpp = MAX2(max_bpp, surf->internal_bpp);
206                }
207        }
208        job->internal_bpp = max_bpp;
209        STATIC_ASSERT(RENDER_TARGET_MAXIMUM_32BPP == 0);
210        tile_size_index += max_bpp;
211
212        assert(tile_size_index < ARRAY_SIZE(tile_sizes));
213        job->tile_width = tile_sizes[tile_size_index * 2 + 0];
214        job->tile_height = tile_sizes[tile_size_index * 2 + 1];
215}
216
217/**
218 * Returns a v3d_job struture for tracking V3D rendering to a particular FBO.
219 *
220 * If we've already started rendering to this FBO, then return the same job,
221 * otherwise make a new one.  If we're beginning rendering to an FBO, make
222 * sure that any previous reads of the FBO (or writes to its color/Z surfaces)
223 * have been flushed.
224 */
225struct v3d_job *
226v3d_get_job(struct v3d_context *v3d,
227            struct pipe_surface **cbufs, struct pipe_surface *zsbuf)
228{
229        /* Return the existing job for this FBO if we have one */
230        struct v3d_job_key local_key = {
231                .cbufs = {
232                        cbufs[0],
233                        cbufs[1],
234                        cbufs[2],
235                        cbufs[3],
236                },
237                .zsbuf = zsbuf,
238        };
239        struct hash_entry *entry = _mesa_hash_table_search(v3d->jobs,
240                                                           &local_key);
241        if (entry)
242                return entry->data;
243
244        /* Creating a new job.  Make sure that any previous jobs reading or
245         * writing these buffers are flushed.
246         */
247        struct v3d_job *job = v3d_job_create(v3d);
248
249        for (int i = 0; i < V3D_MAX_DRAW_BUFFERS; i++) {
250                if (cbufs[i]) {
251                        v3d_flush_jobs_reading_resource(v3d, cbufs[i]->texture);
252                        pipe_surface_reference(&job->cbufs[i], cbufs[i]);
253
254                        if (cbufs[i]->texture->nr_samples > 1)
255                                job->msaa = true;
256                }
257        }
258        if (zsbuf) {
259                v3d_flush_jobs_reading_resource(v3d, zsbuf->texture);
260                pipe_surface_reference(&job->zsbuf, zsbuf);
261                if (zsbuf->texture->nr_samples > 1)
262                        job->msaa = true;
263        }
264
265        for (int i = 0; i < V3D_MAX_DRAW_BUFFERS; i++) {
266                if (cbufs[i])
267                        _mesa_hash_table_insert(v3d->write_jobs,
268                                                cbufs[i]->texture, job);
269        }
270        if (zsbuf) {
271                _mesa_hash_table_insert(v3d->write_jobs, zsbuf->texture, job);
272
273                struct v3d_resource *rsc = v3d_resource(zsbuf->texture);
274                if (rsc->separate_stencil) {
275                        v3d_flush_jobs_reading_resource(v3d,
276                                                        &rsc->separate_stencil->base);
277                        _mesa_hash_table_insert(v3d->write_jobs,
278                                                &rsc->separate_stencil->base,
279                                                job);
280                }
281        }
282
283        memcpy(&job->key, &local_key, sizeof(local_key));
284        _mesa_hash_table_insert(v3d->jobs, &job->key, job);
285
286        return job;
287}
288
289struct v3d_job *
290v3d_get_job_for_fbo(struct v3d_context *v3d)
291{
292        if (v3d->job)
293                return v3d->job;
294
295        struct pipe_surface **cbufs = v3d->framebuffer.cbufs;
296        struct pipe_surface *zsbuf = v3d->framebuffer.zsbuf;
297        struct v3d_job *job = v3d_get_job(v3d, cbufs, zsbuf);
298
299        if (v3d->framebuffer.samples >= 1)
300                job->msaa = true;
301
302        v3d_job_set_tile_buffer_size(job);
303
304        /* The dirty flags are tracking what's been updated while v3d->job has
305         * been bound, so set them all to ~0 when switching between jobs.  We
306         * also need to reset all state at the start of rendering.
307         */
308        v3d->dirty = ~0;
309
310        /* If we're binding to uninitialized buffers, no need to load their
311         * contents before drawing.
312         */
313        for (int i = 0; i < 4; i++) {
314                if (cbufs[i]) {
315                        struct v3d_resource *rsc = v3d_resource(cbufs[i]->texture);
316                        if (!rsc->writes)
317                                job->clear |= PIPE_CLEAR_COLOR0 << i;
318                }
319        }
320
321        if (zsbuf) {
322                struct v3d_resource *rsc = v3d_resource(zsbuf->texture);
323                if (!rsc->writes)
324                        job->clear |= PIPE_CLEAR_DEPTH | PIPE_CLEAR_STENCIL;
325        }
326
327        job->draw_tiles_x = DIV_ROUND_UP(v3d->framebuffer.width,
328                                         job->tile_width);
329        job->draw_tiles_y = DIV_ROUND_UP(v3d->framebuffer.height,
330                                         job->tile_height);
331
332        v3d->job = job;
333
334        return job;
335}
336
337static void
338v3d_clif_dump(struct v3d_context *v3d, struct v3d_job *job)
339{
340        if (!(V3D_DEBUG & (V3D_DEBUG_CL | V3D_DEBUG_CLIF)))
341                return;
342
343        struct clif_dump *clif = clif_dump_init(&v3d->screen->devinfo,
344                                                stderr,
345                                                V3D_DEBUG & V3D_DEBUG_CL);
346
347        set_foreach(job->bos, entry) {
348                struct v3d_bo *bo = (void *)entry->key;
349                char *name = ralloc_asprintf(NULL, "%s_0x%x",
350                                             bo->name, bo->offset);
351
352                v3d_bo_map(bo);
353                clif_dump_add_bo(clif, name, bo->offset, bo->size, bo->map);
354
355                ralloc_free(name);
356        }
357
358        clif_dump(clif, &job->submit);
359
360        clif_dump_destroy(clif);
361}
362
363/**
364 * Submits the job to the kernel and then reinitializes it.
365 */
366void
367v3d_job_submit(struct v3d_context *v3d, struct v3d_job *job)
368{
369        MAYBE_UNUSED struct v3d_screen *screen = v3d->screen;
370
371        if (!job->needs_flush)
372                goto done;
373
374        if (v3d->screen->devinfo.ver >= 41)
375                v3d41_emit_rcl(job);
376        else
377                v3d33_emit_rcl(job);
378
379        if (cl_offset(&job->bcl) > 0) {
380                if (screen->devinfo.ver >= 41)
381                        v3d41_bcl_epilogue(v3d, job);
382                else
383                        v3d33_bcl_epilogue(v3d, job);
384        }
385
386        /* While the RCL will implicitly depend on the last RCL to have
387         * finished, we also need to block on any previous TFU job we may have
388         * dispatched.
389         */
390        job->submit.in_sync_rcl = v3d->out_sync;
391
392        /* Update the sync object for the last rendering by our context. */
393        job->submit.out_sync = v3d->out_sync;
394
395        job->submit.bcl_end = job->bcl.bo->offset + cl_offset(&job->bcl);
396        job->submit.rcl_end = job->rcl.bo->offset + cl_offset(&job->rcl);
397
398        /* On V3D 4.1, the tile alloc/state setup moved to register writes
399         * instead of binner packets.
400         */
401        if (screen->devinfo.ver >= 41) {
402                v3d_job_add_bo(job, job->tile_alloc);
403                job->submit.qma = job->tile_alloc->offset;
404                job->submit.qms = job->tile_alloc->size;
405
406                v3d_job_add_bo(job, job->tile_state);
407                job->submit.qts = job->tile_state->offset;
408        }
409
410        v3d_clif_dump(v3d, job);
411
412        if (!(V3D_DEBUG & V3D_DEBUG_NORAST)) {
413                int ret;
414
415                ret = v3d_ioctl(v3d->fd, DRM_IOCTL_V3D_SUBMIT_CL, &job->submit);
416                static bool warned = false;
417                if (ret && !warned) {
418                        fprintf(stderr, "Draw call returned %s.  "
419                                        "Expect corruption.\n", strerror(errno));
420                        warned = true;
421                }
422        }
423
424done:
425        v3d_job_free(v3d, job);
426}
427
428static bool
429v3d_job_compare(const void *a, const void *b)
430{
431        return memcmp(a, b, sizeof(struct v3d_job_key)) == 0;
432}
433
434static uint32_t
435v3d_job_hash(const void *key)
436{
437        return _mesa_hash_data(key, sizeof(struct v3d_job_key));
438}
439
440void
441v3d_job_init(struct v3d_context *v3d)
442{
443        v3d->jobs = _mesa_hash_table_create(v3d,
444                                            v3d_job_hash,
445                                            v3d_job_compare);
446        v3d->write_jobs = _mesa_hash_table_create(v3d,
447                                                  _mesa_hash_pointer,
448                                                  _mesa_key_pointer_equal);
449}
450
451