v3dv: support submits without a command buffer

It is valid to submit with an empty list ofcommand buffers, however,
we still need to wait on the pWaitSemaphores provided and only signal
the pSignalSemaphores and fence once we have finished waiting on them
to honor the semantics of the submission.

Because waiting and signaling happens in the kernel, the easiest way
to do this is to submit a trivial no-op job to the GPU. To do this,
we need to refactor some of our code so that code that might have been
operating on a command buffer starts operating on a job instead, so we
can resuse most of our infrastructure to create the no-op job.

Additionally, because no-op jobs are created internally by the driver,
we are responsible for destroying them too. For this, we bind a fence
to each no-op job we submit and we test for completion of in-flight
no-op jobs (and destory them if completed) every time vkQueueSubmit
is called.

Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/6766>
This commit is contained in:
Iago Toral Quiroga
2020-03-13 11:35:06 +01:00
committed by Marge Bot
parent d9152cb949
commit 8ed2e53e0d
7 changed files with 346 additions and 103 deletions
+220 -14
View File
@@ -132,7 +132,7 @@ process_fence_to_signal(struct v3dv_device *device, VkFence _fence)
}
static VkResult
queue_submit_job(struct v3dv_job *job, bool do_wait)
queue_submit_job(struct v3dv_queue *queue, struct v3dv_job *job, bool do_wait)
{
assert(job);
@@ -150,10 +150,10 @@ queue_submit_job(struct v3dv_job *job, bool do_wait)
* we have more than one semaphore to wait on.
*/
submit.in_sync_bcl = 0;
submit.in_sync_rcl = do_wait ? job->cmd_buffer->device->last_job_sync : 0;
submit.in_sync_rcl = do_wait ? queue->device->last_job_sync : 0;
/* Update the sync object for the last rendering by this device. */
submit.out_sync = job->cmd_buffer->device->last_job_sync;
submit.out_sync = queue->device->last_job_sync;
submit.bcl_start = job->bcl.bo->offset;
submit.bcl_end = job->bcl.bo->offset + v3dv_cl_offset(&job->bcl);
@@ -182,10 +182,10 @@ queue_submit_job(struct v3dv_job *job, bool do_wait)
assert(bo_idx == submit.bo_handle_count);
submit.bo_handles = (uintptr_t)(void *)bo_handles;
struct v3dv_device *device = job->cmd_buffer->device;
v3dv_clif_dump(device, job, &submit);
v3dv_clif_dump(queue->device, job, &submit);
int ret = v3dv_ioctl(device->render_fd, DRM_IOCTL_V3D_SUBMIT_CL, &submit);
int ret = v3dv_ioctl(queue->device->render_fd,
DRM_IOCTL_V3D_SUBMIT_CL, &submit);
static bool warned = false;
if (ret && !warned) {
fprintf(stderr, "Draw call returned %s. Expect corruption.\n",
@@ -202,12 +202,14 @@ queue_submit_job(struct v3dv_job *job, bool do_wait)
}
static VkResult
queue_submit_cmd_buffer(struct v3dv_cmd_buffer *cmd_buffer,
queue_submit_cmd_buffer(struct v3dv_queue *queue,
struct v3dv_cmd_buffer *cmd_buffer,
const VkSubmitInfo *pSubmit)
{
list_for_each_entry_safe(struct v3dv_job, job,
&cmd_buffer->submit_jobs, list_link) {
VkResult result = queue_submit_job(job, pSubmit->waitSemaphoreCount > 0);
VkResult result = queue_submit_job(queue, job,
pSubmit->waitSemaphoreCount > 0);
if (result != VK_SUCCESS)
return result;
}
@@ -215,20 +217,222 @@ queue_submit_cmd_buffer(struct v3dv_cmd_buffer *cmd_buffer,
return VK_SUCCESS;
}
static void
emit_noop_bin(struct v3dv_job *job)
{
v3dv_job_start_frame(job, 1, 1, 1, 1, V3D_INTERNAL_BPP_32);
v3dv_job_emit_binning_flush(job);
}
static void
emit_noop_render(struct v3dv_job *job)
{
struct v3dv_cl *rcl = &job->rcl;
v3dv_cl_ensure_space_with_branch(rcl, 200 + 1 * 256 *
cl_packet_length(SUPERTILE_COORDINATES));
cl_emit(rcl, TILE_RENDERING_MODE_CFG_COMMON, config) {
config.early_z_disable = true;
config.image_width_pixels = 1;
config.image_height_pixels = 1;
config.number_of_render_targets = 1;
config.multisample_mode_4x = false;
config.maximum_bpp_of_all_render_targets = V3D_INTERNAL_BPP_32;
}
cl_emit(rcl, TILE_RENDERING_MODE_CFG_COLOR, rt) {
rt.render_target_0_internal_bpp = V3D_INTERNAL_BPP_32;
rt.render_target_0_internal_type = V3D_INTERNAL_TYPE_8;
rt.render_target_0_clamp = V3D_RENDER_TARGET_CLAMP_NONE;
}
cl_emit(rcl, TILE_RENDERING_MODE_CFG_ZS_CLEAR_VALUES, clear) {
clear.z_clear_value = 1.0f;
clear.stencil_clear_value = 0;
};
cl_emit(rcl, TILE_LIST_INITIAL_BLOCK_SIZE, init) {
init.use_auto_chained_tile_lists = true;
init.size_of_first_block_in_chained_tile_lists =
TILE_ALLOCATION_BLOCK_SIZE_64B;
}
cl_emit(rcl, MULTICORE_RENDERING_TILE_LIST_SET_BASE, list) {
list.address = v3dv_cl_address(job->tile_alloc, 0);
}
cl_emit(rcl, MULTICORE_RENDERING_SUPERTILE_CFG, config) {
config.number_of_bin_tile_lists = 1;
config.total_frame_width_in_tiles = 1;
config.total_frame_height_in_tiles = 1;
config.supertile_width_in_tiles = 1;
config.supertile_height_in_tiles = 1;
config.total_frame_width_in_supertiles = 1;
config.total_frame_height_in_supertiles = 1;
}
struct v3dv_cl *icl = &job->indirect;
v3dv_cl_ensure_space(icl, 200, 1);
struct v3dv_cl_reloc tile_list_start = v3dv_cl_get_address(icl);
cl_emit(icl, TILE_COORDINATES_IMPLICIT, coords);
cl_emit(icl, END_OF_LOADS, end);
cl_emit(icl, BRANCH_TO_IMPLICIT_TILE_LIST, branch);
cl_emit(icl, STORE_TILE_BUFFER_GENERAL, store) {
store.buffer_to_store = NONE;
}
cl_emit(icl, END_OF_TILE_MARKER, end);
cl_emit(icl, RETURN_FROM_SUB_LIST, ret);
cl_emit(rcl, START_ADDRESS_OF_GENERIC_TILE_LIST, branch) {
branch.start = tile_list_start;
branch.end = v3dv_cl_get_address(icl);
}
cl_emit(rcl, SUPERTILE_COORDINATES, coords) {
coords.column_number_in_supertiles = 0;
coords.row_number_in_supertiles = 0;
}
cl_emit(rcl, END_OF_RENDERING, end);
}
static VkResult
queue_create_noop_job(struct v3dv_queue *queue, struct v3dv_job **job)
{
*job = vk_zalloc(&queue->device->alloc, sizeof(struct v3dv_job), 8,
VK_SYSTEM_ALLOCATION_SCOPE_OBJECT);
if (!*job)
return VK_ERROR_OUT_OF_HOST_MEMORY;
v3dv_job_init(*job, queue->device, NULL, -1);
emit_noop_bin(*job);
emit_noop_render(*job);
return VK_SUCCESS;
}
void
v3dv_queue_destroy_completed_noop_jobs(struct v3dv_queue *queue)
{
struct v3dv_device *device = queue->device;
VkDevice _device = v3dv_device_to_handle(device);
list_for_each_entry_safe(struct v3dv_job, job,
&queue->noop_jobs, list_link) {
assert(job->fence);
if (!drmSyncobjWait(device->render_fd, &job->fence->sync, 1, 0, 0, NULL)) {
v3dv_job_destroy(job);
v3dv_DestroyFence(_device, v3dv_fence_to_handle(job->fence), NULL);
}
}
}
static VkResult
queue_submit_noop_job(struct v3dv_queue *queue, const VkSubmitInfo *pSubmit)
{
VkResult result;
bool can_destroy_job = true;
struct v3dv_device *device = queue->device;
VkDevice _device = v3dv_device_to_handle(device);
/* Create noop job */
struct v3dv_job *job;
result = queue_create_noop_job(queue, &job);
if (result != VK_SUCCESS)
goto fail_job;
/* Create a fence for the job */
VkFence _fence;
VkFenceCreateInfo fence_info = {
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
.pNext = NULL,
.flags = 0
};
result = v3dv_CreateFence(_device, &fence_info, NULL, &_fence);
if (result != VK_SUCCESS)
goto fail_fence;
/* Submit the job */
result = queue_submit_job(queue, job, pSubmit->waitSemaphoreCount > 0);
if (result != VK_SUCCESS)
goto fail_submit;
list_addtail(&job->list_link, &queue->noop_jobs);
/* At this point we have submitted the job for execution and we can no
* longer destroy it until we know it has completed execution on the GPU.
*/
can_destroy_job = false;
/* Bind a fence to the job we have just submitted so we can poll if the job
* has completed.
*/
if (process_fence_to_signal(device, _fence) != VK_SUCCESS) {
/* If we could not bind the fence, then we need to do a sync wait so
* we don't leak the job. If the sync wait also fails, then we are
* out of options.
*/
int ret = drmSyncobjWait(device->render_fd,
&device->last_job_sync, 1, INT64_MAX, 0, NULL);
if (!ret)
can_destroy_job = true;
else
result = VK_ERROR_DEVICE_LOST;
goto fail_signal_fence;
}
job->fence = v3dv_fence_from_handle(_fence);
return result;
fail_signal_fence:
fail_submit:
v3dv_DestroyFence(_device, _fence, NULL);
fail_fence:
if (can_destroy_job)
v3dv_job_destroy(job);
fail_job:
return result;
}
static VkResult
queue_submit_cmd_buffer_batch(struct v3dv_queue *queue,
const VkSubmitInfo *pSubmit,
VkFence fence)
{
VkResult result = VK_SUCCESS;
for (uint32_t i = 0; i < pSubmit->commandBufferCount; i++) {
struct v3dv_cmd_buffer *cmd_buffer =
v3dv_cmd_buffer_from_handle(pSubmit->pCommandBuffers[i]);
result = queue_submit_cmd_buffer(cmd_buffer, pSubmit);
if (result != VK_SUCCESS)
return result;
/* Even if we don't have any actual work to submit we still need to wait
* on the wait semaphores and signal the signal semaphores and fence, so
* in this scenario we just submit a trivial no-op job so we don't have
* to do anything special, it should not be a common case anyway.
*/
if (pSubmit->commandBufferCount == 0) {
result = queue_submit_noop_job(queue, pSubmit);
} else {
for (uint32_t i = 0; i < pSubmit->commandBufferCount; i++) {
struct v3dv_cmd_buffer *cmd_buffer =
v3dv_cmd_buffer_from_handle(pSubmit->pCommandBuffers[i]);
result = queue_submit_cmd_buffer(queue, cmd_buffer, pSubmit);
if (result != VK_SUCCESS)
break;
}
}
if (result != VK_SUCCESS)
return result;
result = process_semaphores_to_signal(queue->device,
pSubmit->signalSemaphoreCount,
pSubmit->pSignalSemaphores);
@@ -246,6 +450,8 @@ v3dv_QueueSubmit(VkQueue _queue,
{
V3DV_FROM_HANDLE(v3dv_queue, queue, _queue);
v3dv_queue_destroy_completed_noop_jobs(queue);
VkResult result = VK_SUCCESS;
for (uint32_t i = 0; i < submitCount; i++) {
result = queue_submit_cmd_buffer_batch(queue, &pSubmits[i], fence);