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