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 vc5_job.c 25 * 26 * Functions for submitting VC5 render jobs to the kernel. 27 */ 28 29 #include <xf86drm.h> 30 #include "vc5_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 vc5_job_free(struct vc5_context *vc5, struct vc5_job *job) 49 { 50 struct set_entry *entry; 51 52 set_foreach(job->bos, entry) { 53 struct vc5_bo *bo = (struct vc5_bo *)entry->key; 54 vc5_bo_unreference(&bo); 55 } 56 57 remove_from_ht(vc5->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(vc5->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(vc5->write_jobs, job->cbufs[i]->texture); 72 pipe_surface_reference(&job->cbufs[i], NULL); 73 } 74 } 75 if (job->zsbuf) { 76 remove_from_ht(vc5->write_jobs, job->zsbuf->texture); 77 pipe_surface_reference(&job->zsbuf, NULL); 78 } 79 80 if (vc5->job == job) 81 vc5->job = NULL; 82 83 vc5_destroy_cl(&job->bcl); 84 vc5_destroy_cl(&job->rcl); 85 vc5_destroy_cl(&job->indirect); 86 vc5_bo_unreference(&job->tile_alloc); 87 vc5_bo_unreference(&job->tile_state); 88 89 ralloc_free(job); 90 } 91 92 static struct vc5_job * 93 vc5_job_create(struct vc5_context *vc5) 94 { 95 struct vc5_job *job = rzalloc(vc5, struct vc5_job); 96 97 job->vc5 = vc5; 98 99 vc5_init_cl(job, &job->bcl); 100 vc5_init_cl(job, &job->rcl); 101 vc5_init_cl(job, &job->indirect); 102 103 job->draw_min_x = ~0; 104 job->draw_min_y = ~0; 105 job->draw_max_x = 0; 106 job->draw_max_y = 0; 107 108 job->bos = _mesa_set_create(job, 109 _mesa_hash_pointer, 110 _mesa_key_pointer_equal); 111 return job; 112 } 113 114 void 115 vc5_job_add_bo(struct vc5_job *job, struct vc5_bo *bo) 116 { 117 if (!bo) 118 return; 119 120 if (_mesa_set_search(job->bos, bo)) 121 return; 122 123 vc5_bo_reference(bo); 124 _mesa_set_add(job->bos, bo); 125 job->referenced_size += bo->size; 126 127 uint32_t *bo_handles = (void *)(uintptr_t)job->submit.bo_handles; 128 129 if (job->submit.bo_handle_count >= job->bo_handles_size) { 130 job->bo_handles_size = MAX2(4, job->bo_handles_size * 2); 131 bo_handles = reralloc(job, bo_handles, 132 uint32_t, job->bo_handles_size); 133 job->submit.bo_handles = (uintptr_t)(void *)bo_handles; 134 } 135 bo_handles[job->submit.bo_handle_count++] = bo->handle; 136 } 137 138 void 139 vc5_job_add_write_resource(struct vc5_job *job, struct pipe_resource *prsc) 140 { 141 struct vc5_context *vc5 = job->vc5; 142 143 if (!job->write_prscs) { 144 job->write_prscs = _mesa_set_create(job, 145 _mesa_hash_pointer, 146 _mesa_key_pointer_equal); 147 } 148 149 _mesa_set_add(job->write_prscs, prsc); 150 _mesa_hash_table_insert(vc5->write_jobs, prsc, job); 151 } 152 153 void 154 vc5_flush_jobs_writing_resource(struct vc5_context *vc5, 155 struct pipe_resource *prsc) 156 { 157 struct hash_entry *entry = _mesa_hash_table_search(vc5->write_jobs, 158 prsc); 159 if (entry) { 160 struct vc5_job *job = entry->data; 161 vc5_job_submit(vc5, job); 162 } 163 } 164 165 void 166 vc5_flush_jobs_reading_resource(struct vc5_context *vc5, 167 struct pipe_resource *prsc) 168 { 169 struct vc5_resource *rsc = vc5_resource(prsc); 170 171 vc5_flush_jobs_writing_resource(vc5, prsc); 172 173 struct hash_entry *entry; 174 hash_table_foreach(vc5->jobs, entry) { 175 struct vc5_job *job = entry->data; 176 177 if (_mesa_set_search(job->bos, rsc->bo)) { 178 vc5_job_submit(vc5, job); 179 /* Reminder: vc5->jobs is safe to keep iterating even 180 * after deletion of an entry. 181 */ 182 continue; 183 } 184 } 185 } 186 187 static void 188 vc5_job_set_tile_buffer_size(struct vc5_job *job) 189 { 190 static const uint8_t tile_sizes[] = { 191 64, 64, 192 64, 32, 193 32, 32, 194 32, 16, 195 16, 16, 196 }; 197 int tile_size_index = 0; 198 if (job->msaa) 199 tile_size_index += 2; 200 201 if (job->cbufs[3] || job->cbufs[2]) 202 tile_size_index += 2; 203 else if (job->cbufs[1]) 204 tile_size_index++; 205 206 int max_bpp = RENDER_TARGET_MAXIMUM_32BPP; 207 for (int i = 0; i < VC5_MAX_DRAW_BUFFERS; i++) { 208 if (job->cbufs[i]) { 209 struct vc5_surface *surf = vc5_surface(job->cbufs[i]); 210 max_bpp = MAX2(max_bpp, surf->internal_bpp); 211 } 212 } 213 job->internal_bpp = max_bpp; 214 STATIC_ASSERT(RENDER_TARGET_MAXIMUM_32BPP == 0); 215 tile_size_index += max_bpp; 216 217 assert(tile_size_index < ARRAY_SIZE(tile_sizes)); 218 job->tile_width = tile_sizes[tile_size_index * 2 + 0]; 219 job->tile_height = tile_sizes[tile_size_index * 2 + 1]; 220 } 221 222 /** 223 * Returns a vc5_job struture for tracking V3D rendering to a particular FBO. 224 * 225 * If we've already started rendering to this FBO, then return old same job, 226 * otherwise make a new one. If we're beginning rendering to an FBO, make 227 * sure that any previous reads of the FBO (or writes to its color/Z surfaces) 228 * have been flushed. 229 */ 230 struct vc5_job * 231 vc5_get_job(struct vc5_context *vc5, 232 struct pipe_surface **cbufs, struct pipe_surface *zsbuf) 233 { 234 /* Return the existing job for this FBO if we have one */ 235 struct vc5_job_key local_key = { 236 .cbufs = { 237 cbufs[0], 238 cbufs[1], 239 cbufs[2], 240 cbufs[3], 241 }, 242 .zsbuf = zsbuf, 243 }; 244 struct hash_entry *entry = _mesa_hash_table_search(vc5->jobs, 245 &local_key); 246 if (entry) 247 return entry->data; 248 249 /* Creating a new job. Make sure that any previous jobs reading or 250 * writing these buffers are flushed. 251 */ 252 struct vc5_job *job = vc5_job_create(vc5); 253 254 for (int i = 0; i < VC5_MAX_DRAW_BUFFERS; i++) { 255 if (cbufs[i]) { 256 vc5_flush_jobs_reading_resource(vc5, cbufs[i]->texture); 257 pipe_surface_reference(&job->cbufs[i], cbufs[i]); 258 259 if (cbufs[i]->texture->nr_samples > 1) 260 job->msaa = true; 261 } 262 } 263 if (zsbuf) { 264 vc5_flush_jobs_reading_resource(vc5, zsbuf->texture); 265 pipe_surface_reference(&job->zsbuf, zsbuf); 266 if (zsbuf->texture->nr_samples > 1) 267 job->msaa = true; 268 } 269 270 vc5_job_set_tile_buffer_size(job); 271 272 for (int i = 0; i < VC5_MAX_DRAW_BUFFERS; i++) { 273 if (cbufs[i]) 274 _mesa_hash_table_insert(vc5->write_jobs, 275 cbufs[i]->texture, job); 276 } 277 if (zsbuf) 278 _mesa_hash_table_insert(vc5->write_jobs, zsbuf->texture, job); 279 280 memcpy(&job->key, &local_key, sizeof(local_key)); 281 _mesa_hash_table_insert(vc5->jobs, &job->key, job); 282 283 return job; 284 } 285 286 struct vc5_job * 287 vc5_get_job_for_fbo(struct vc5_context *vc5) 288 { 289 if (vc5->job) 290 return vc5->job; 291 292 struct pipe_surface **cbufs = vc5->framebuffer.cbufs; 293 struct pipe_surface *zsbuf = vc5->framebuffer.zsbuf; 294 struct vc5_job *job = vc5_get_job(vc5, cbufs, zsbuf); 295 296 /* The dirty flags are tracking what's been updated while vc5->job has 297 * been bound, so set them all to ~0 when switching between jobs. We 298 * also need to reset all state at the start of rendering. 299 */ 300 vc5->dirty = ~0; 301 302 /* If we're binding to uninitialized buffers, no need to load their 303 * contents before drawing. 304 */ 305 for (int i = 0; i < 4; i++) { 306 if (cbufs[i]) { 307 struct vc5_resource *rsc = vc5_resource(cbufs[i]->texture); 308 if (!rsc->writes) 309 job->cleared |= PIPE_CLEAR_COLOR0 << i; 310 } 311 } 312 313 if (zsbuf) { 314 struct vc5_resource *rsc = vc5_resource(zsbuf->texture); 315 if (!rsc->writes) 316 job->cleared |= PIPE_CLEAR_DEPTH | PIPE_CLEAR_STENCIL; 317 } 318 319 job->draw_tiles_x = DIV_ROUND_UP(vc5->framebuffer.width, 320 job->tile_width); 321 job->draw_tiles_y = DIV_ROUND_UP(vc5->framebuffer.height, 322 job->tile_height); 323 324 vc5->job = job; 325 326 return job; 327 } 328 329 static bool 330 vc5_clif_dump_lookup(void *data, uint32_t addr, void **vaddr) 331 { 332 struct vc5_job *job = data; 333 struct set_entry *entry; 334 335 set_foreach(job->bos, entry) { 336 struct vc5_bo *bo = (void *)entry->key; 337 338 if (addr >= bo->offset && 339 addr < bo->offset + bo->size) { 340 vc5_bo_map(bo); 341 *vaddr = bo->map + addr - bo->offset; 342 return true; 343 } 344 } 345 346 return false; 347 } 348 349 static void 350 vc5_clif_dump(struct vc5_context *vc5, struct vc5_job *job) 351 { 352 if (!(V3D_DEBUG & V3D_DEBUG_CL)) 353 return; 354 355 struct clif_dump *clif = clif_dump_init(&vc5->screen->devinfo, 356 stderr, vc5_clif_dump_lookup, 357 job); 358 359 fprintf(stderr, "BCL: 0x%08x..0x%08x\n", 360 job->submit.bcl_start, job->submit.bcl_end); 361 362 clif_dump_add_cl(clif, job->submit.bcl_start, job->submit.bcl_end); 363 364 fprintf(stderr, "RCL: 0x%08x..0x%08x\n", 365 job->submit.rcl_start, job->submit.rcl_end); 366 clif_dump_add_cl(clif, job->submit.rcl_start, job->submit.rcl_end); 367 } 368 369 /** 370 * Submits the job to the kernel and then reinitializes it. 371 */ 372 void 373 vc5_job_submit(struct vc5_context *vc5, struct vc5_job *job) 374 { 375 MAYBE_UNUSED struct vc5_screen *screen = vc5->screen; 376 377 if (!job->needs_flush) 378 goto done; 379 380 /* The RCL setup would choke if the draw bounds cause no drawing, so 381 * just drop the drawing if that's the case. 382 */ 383 if (job->draw_max_x <= job->draw_min_x || 384 job->draw_max_y <= job->draw_min_y) { 385 goto done; 386 } 387 388 if (vc5->screen->devinfo.ver >= 41) 389 v3d41_emit_rcl(job); 390 else 391 v3d33_emit_rcl(job); 392 393 if (cl_offset(&job->bcl) > 0) { 394 vc5_cl_ensure_space_with_branch(&job->bcl, 395 7 + 396 cl_packet_length(OCCLUSION_QUERY_COUNTER)); 397 398 if (job->oq_enabled) { 399 /* Disable the OQ at the end of the CL, so that the 400 * draw calls at the start of the CL don't inherit the 401 * OQ counter. 402 */ 403 cl_emit(&job->bcl, OCCLUSION_QUERY_COUNTER, counter); 404 } 405 406 /* Increment the semaphore indicating that binning is done and 407 * unblocking the render thread. Note that this doesn't act 408 * until the FLUSH completes. 409 */ 410 cl_emit(&job->bcl, INCREMENT_SEMAPHORE, incr); 411 412 /* The FLUSH_ALL emits any unwritten state changes in each 413 * tile. We can use this to reset any state that needs to be 414 * present at the start of the next tile, as we do with 415 * OCCLUSION_QUERY_COUNTER above. 416 */ 417 cl_emit(&job->bcl, FLUSH_ALL_STATE, flush); 418 } 419 420 job->submit.bcl_end = job->bcl.bo->offset + cl_offset(&job->bcl); 421 job->submit.rcl_end = job->rcl.bo->offset + cl_offset(&job->rcl); 422 423 /* On V3D 4.1, the tile alloc/state setup moved to register writes 424 * instead of binner pac`kets. 425 */ 426 if (screen->devinfo.ver >= 41) { 427 vc5_job_add_bo(job, job->tile_alloc); 428 job->submit.qma = job->tile_alloc->offset; 429 job->submit.qms = job->tile_alloc->size; 430 431 vc5_job_add_bo(job, job->tile_state); 432 job->submit.qts = job->tile_state->offset; 433 } 434 435 vc5_clif_dump(vc5, job); 436 437 if (!(V3D_DEBUG & V3D_DEBUG_NORAST)) { 438 int ret; 439 440 #ifndef USE_VC5_SIMULATOR 441 ret = drmIoctl(vc5->fd, DRM_IOCTL_VC5_SUBMIT_CL, &job->submit); 442 #else 443 ret = vc5_simulator_flush(vc5, &job->submit, job); 444 #endif 445 static bool warned = false; 446 if (ret && !warned) { 447 fprintf(stderr, "Draw call returned %s. " 448 "Expect corruption.\n", strerror(errno)); 449 warned = true; 450 } 451 } 452 453 if (vc5->last_emit_seqno - vc5->screen->finished_seqno > 5) { 454 if (!vc5_wait_seqno(vc5->screen, 455 vc5->last_emit_seqno - 5, 456 PIPE_TIMEOUT_INFINITE, 457 "job throttling")) { 458 fprintf(stderr, "Job throttling failed\n"); 459 } 460 } 461 462 done: 463 vc5_job_free(vc5, job); 464 } 465 466 static bool 467 vc5_job_compare(const void *a, const void *b) 468 { 469 return memcmp(a, b, sizeof(struct vc5_job_key)) == 0; 470 } 471 472 static uint32_t 473 vc5_job_hash(const void *key) 474 { 475 return _mesa_hash_data(key, sizeof(struct vc5_job_key)); 476 } 477 478 void 479 vc5_job_init(struct vc5_context *vc5) 480 { 481 vc5->jobs = _mesa_hash_table_create(vc5, 482 vc5_job_hash, 483 vc5_job_compare); 484 vc5->write_jobs = _mesa_hash_table_create(vc5, 485 _mesa_hash_pointer, 486 _mesa_key_pointer_equal); 487 } 488 489