|
7 | 7 | #include "headset/headset.h"
|
8 | 8 | #include "math/math.h"
|
9 | 9 | #include "core/gpu.h"
|
| 10 | +#include "core/job.h" |
10 | 11 | #include "core/maf.h"
|
11 | 12 | #include "core/spv.h"
|
12 | 13 | #include "core/os.h"
|
@@ -554,9 +555,11 @@ struct Pass {
|
554 | 555 |
|
555 | 556 | typedef struct PipelineJob {
|
556 | 557 | struct PipelineJob* next;
|
| 558 | + job* handle; |
557 | 559 | uint64_t hash;
|
558 | 560 | gpu_pipeline_info* info;
|
559 | 561 | gpu_pipeline* pipeline;
|
| 562 | + char* error; |
560 | 563 | } PipelineJob;
|
561 | 564 |
|
562 | 565 | static thread_local struct {
|
@@ -595,7 +598,7 @@ static struct {
|
595 | 598 | Readback* newestReadback;
|
596 | 599 | MaterialBlock* materials;
|
597 | 600 | BufferAllocator bufferAllocators[4];
|
598 |
| - PipelineJob* pipelineJobs; |
| 601 | + PipelineJob* newPipelines; |
599 | 602 | map_t pipelineLookup;
|
600 | 603 | gpu_pipeline* pipelines;
|
601 | 604 | uint32_t pipelineCount;
|
@@ -1120,13 +1123,100 @@ static bool recordComputePass(Pass* pass, gpu_stream* stream) {
|
1120 | 1123 | return true;
|
1121 | 1124 | }
|
1122 | 1125 |
|
| 1126 | +static void compilePipeline(void* arg) { |
| 1127 | + PipelineJob* job = arg; |
| 1128 | + if (!gpu_pipeline_init_graphics(job->pipeline, job->info, NULL)) { |
| 1129 | + const char* error = gpu_get_error(); |
| 1130 | + size_t length = strlen(error); |
| 1131 | + job->error = lovrMalloc(length + 1); |
| 1132 | + memcpy(job->error, error, length + 1); |
| 1133 | + } |
| 1134 | +} |
| 1135 | + |
1123 | 1136 | static bool recordRenderPass(Pass* pass, gpu_stream* stream) {
|
1124 | 1137 | Canvas* canvas = &pass->canvas;
|
1125 | 1138 |
|
1126 | 1139 | if (!canvas->color->texture && !canvas->depth.texture) {
|
1127 | 1140 | return true;
|
1128 | 1141 | }
|
1129 | 1142 |
|
| 1143 | + // Pipelines |
| 1144 | + |
| 1145 | + Draw* draw = pass->draws; |
| 1146 | + Draw* previous = NULL; |
| 1147 | + |
| 1148 | + // List of pipeline jobs offloaded to worker threads (we must wait for them before doing draws) |
| 1149 | + PipelineJob* pipelineJobs = NULL; |
| 1150 | + |
| 1151 | + for (uint32_t i = 0; i < pass->drawCount; i++, previous = draw, draw++) { |
| 1152 | + // If the draw uses the same pipeline as the previous draw, use that |
| 1153 | + if (previous && draw->pipelineInfo == previous->pipelineInfo) { |
| 1154 | + draw->pipeline = previous->pipeline; |
| 1155 | + continue; |
| 1156 | + } |
| 1157 | + |
| 1158 | + // Otherwise, look up the pipeline in the global lookup table |
| 1159 | + uint64_t hash = hash64(draw->pipelineInfo, sizeof(gpu_pipeline_info)); |
| 1160 | + uint64_t value = map_get(&state.pipelineLookup, hash); |
| 1161 | + |
| 1162 | + if (value != MAP_NIL) { |
| 1163 | + draw->pipeline = (gpu_pipeline*) (uintptr_t) value; |
| 1164 | + continue; |
| 1165 | + } |
| 1166 | + |
| 1167 | + // If it's not in the global lookup, search through the linked list of new pipelines |
| 1168 | + PipelineJob* node = atomic_load(&state.newPipelines); |
| 1169 | + bool found = false; |
| 1170 | + |
| 1171 | + while (node) { |
| 1172 | + if (node->hash == hash) { |
| 1173 | + draw->pipeline = node->pipeline; |
| 1174 | + found = true; |
| 1175 | + break; |
| 1176 | + } else { |
| 1177 | + node = node->next; |
| 1178 | + } |
| 1179 | + } |
| 1180 | + |
| 1181 | + if (found) { |
| 1182 | + continue; |
| 1183 | + } |
| 1184 | + |
| 1185 | + // If we couldn't find a pipeline to use, compile a new one |
| 1186 | + uint32_t index = atomic_fetch_add(&state.pipelineCount, 1); |
| 1187 | + |
| 1188 | + if (index >= MAX_PIPELINES) { |
| 1189 | + lovrSetError("Too many pipelines!"); |
| 1190 | + return false; |
| 1191 | + } |
| 1192 | + |
| 1193 | + PipelineJob* job = allocate(&thread.stack, sizeof(PipelineJob)); |
| 1194 | + job->next = NULL; |
| 1195 | + job->handle = NULL; |
| 1196 | + job->hash = hash; |
| 1197 | + job->info = draw->pipelineInfo; |
| 1198 | + job->pipeline = getPipeline(index); |
| 1199 | + job->error = NULL; |
| 1200 | + |
| 1201 | + bool slow; |
| 1202 | + lovrAssert(gpu_pipeline_init_graphics(job->pipeline, job->info, &slow), "Failed to create GPU pipeline: %s", gpu_get_error()); |
| 1203 | + |
| 1204 | + if (slow) { |
| 1205 | + // The pipeline is going to be slow to compile, offload it to a worker thread |
| 1206 | + job->handle = job_start(compilePipeline, job); |
| 1207 | + job->next = pipelineJobs; |
| 1208 | + pipelineJobs = job; |
| 1209 | + } else { |
| 1210 | + // Chain the new pipeline on to the list of new pipelines |
| 1211 | + job->next = atomic_load(&state.newPipelines); |
| 1212 | + while (!atomic_compare_exchange_strong(&state.newPipelines, &job->next, job)) { |
| 1213 | + continue; |
| 1214 | + } |
| 1215 | + } |
| 1216 | + |
| 1217 | + draw->pipeline = job->pipeline; |
| 1218 | + } |
| 1219 | + |
1130 | 1220 | // Render area
|
1131 | 1221 |
|
1132 | 1222 | uint32_t min[2] = { ~0u, ~0u };
|
@@ -1315,70 +1405,6 @@ static bool recordRenderPass(Pass* pass, gpu_stream* stream) {
|
1315 | 1405 | gpu_bundle* builtinBundle = getBundle(state.builtinLayout, builtins, COUNTOF(builtins));
|
1316 | 1406 | if (!builtinBundle) return false;
|
1317 | 1407 |
|
1318 |
| - // Pipelines |
1319 |
| - |
1320 |
| - Draw* draw = pass->draws; |
1321 |
| - Draw* previous = NULL; |
1322 |
| - |
1323 |
| - for (uint32_t i = 0; i < pass->drawCount; i++, previous = draw, draw++) { |
1324 |
| - // If the draw uses the same pipeline as the previous draw, use that |
1325 |
| - if (previous && draw->pipelineInfo == previous->pipelineInfo) { |
1326 |
| - draw->pipeline = previous->pipeline; |
1327 |
| - continue; |
1328 |
| - } |
1329 |
| - |
1330 |
| - // Otherwise, look up the pipeline in the global lookup table |
1331 |
| - uint64_t hash = hash64(draw->pipelineInfo, sizeof(gpu_pipeline_info)); |
1332 |
| - uint64_t value = map_get(&state.pipelineLookup, hash); |
1333 |
| - |
1334 |
| - if (value != MAP_NIL) { |
1335 |
| - draw->pipeline = (gpu_pipeline*) (uintptr_t) value; |
1336 |
| - continue; |
1337 |
| - } |
1338 |
| - |
1339 |
| - // If it's not in the global lookup, search through the linked list of new pipelines |
1340 |
| - PipelineJob* node = atomic_load(&state.pipelineJobs); |
1341 |
| - bool found = false; |
1342 |
| - |
1343 |
| - while (node) { |
1344 |
| - if (node->hash == hash) { |
1345 |
| - draw->pipeline = node->pipeline; |
1346 |
| - found = true; |
1347 |
| - break; |
1348 |
| - } else { |
1349 |
| - node = node->next; |
1350 |
| - } |
1351 |
| - } |
1352 |
| - |
1353 |
| - if (found) { |
1354 |
| - continue; |
1355 |
| - } |
1356 |
| - |
1357 |
| - // If we couldn't find a pipeline to use, compile a new one |
1358 |
| - uint32_t index = atomic_fetch_add(&state.pipelineCount, 1); |
1359 |
| - |
1360 |
| - if (index >= MAX_PIPELINES) { |
1361 |
| - lovrSetError("Too many pipelines!"); |
1362 |
| - return false; |
1363 |
| - } |
1364 |
| - |
1365 |
| - PipelineJob* job = allocate(&thread.stack, sizeof(PipelineJob)); |
1366 |
| - job->next = NULL; |
1367 |
| - job->hash = hash; |
1368 |
| - job->info = draw->pipelineInfo; |
1369 |
| - job->pipeline = getPipeline(index); |
1370 |
| - |
1371 |
| - lovrAssert(gpu_pipeline_init_graphics(job->pipeline, job->info, NULL), "Failed to create GPU pipeline: %s", gpu_get_error()); |
1372 |
| - |
1373 |
| - // Chain the new pipeline on to the list of new pipelines |
1374 |
| - job->next = atomic_load(&state.pipelineJobs); |
1375 |
| - while (!atomic_compare_exchange_strong(&state.pipelineJobs, &job->next, job)) { |
1376 |
| - continue; |
1377 |
| - } |
1378 |
| - |
1379 |
| - draw->pipeline = job->pipeline; |
1380 |
| - } |
1381 |
| - |
1382 | 1408 | // Bundles
|
1383 | 1409 |
|
1384 | 1410 | Draw* prev = NULL;
|
@@ -1451,6 +1477,32 @@ static bool recordRenderPass(Pass* pass, gpu_stream* stream) {
|
1451 | 1477 | gpu_render_begin(stream, &pass->target);
|
1452 | 1478 | gpu_bind_vertex_buffers(stream, &state.defaultBuffer->gpu, &state.defaultBuffer->base, 1, 1);
|
1453 | 1479 |
|
| 1480 | + bool hasError = false; |
| 1481 | + |
| 1482 | + // Wait for any in-progress pipeline jobs to finish |
| 1483 | + while (pipelineJobs) { |
| 1484 | + PipelineJob* job = pipelineJobs; |
| 1485 | + job_wait(job->handle); |
| 1486 | + |
| 1487 | + if (job->error) { |
| 1488 | + if (!hasError) { |
| 1489 | + lovrSetError("Failed to compile GPU pipeline: %s", job->error); |
| 1490 | + hasError = true; |
| 1491 | + } |
| 1492 | + lovrFree(job->error); |
| 1493 | + } |
| 1494 | + |
| 1495 | + pipelineJobs = job->next; |
| 1496 | + job->next = atomic_load(&state.newPipelines); |
| 1497 | + while (!atomic_compare_exchange_strong(&state.newPipelines, &job->next, job)) { |
| 1498 | + continue; |
| 1499 | + } |
| 1500 | + } |
| 1501 | + |
| 1502 | + if (hasError) { |
| 1503 | + return false; |
| 1504 | + } |
| 1505 | + |
1454 | 1506 | for (uint32_t i = 0; i < activeDrawCount; i++) {
|
1455 | 1507 | Draw* draw = &pass->draws[activeDraws[i]];
|
1456 | 1508 |
|
@@ -1889,13 +1941,13 @@ bool lovrGraphicsSubmit(Pass** passes, uint32_t count) {
|
1889 | 1941 | }
|
1890 | 1942 |
|
1891 | 1943 | // Merge any new pipelines into the global pipeline lookup
|
1892 |
| - for (PipelineJob* job = atomic_load(&state.pipelineJobs); job; job = job->next) { |
| 1944 | + for (PipelineJob* job = atomic_load(&state.newPipelines); job; job = job->next) { |
1893 | 1945 | if (map_get(&state.pipelineLookup, job->hash) == MAP_NIL) {
|
1894 | 1946 | map_set(&state.pipelineLookup, job->hash, (uint64_t) (uintptr_t) job->pipeline);
|
1895 | 1947 | }
|
1896 | 1948 | }
|
1897 | 1949 |
|
1898 |
| - atomic_store(&state.pipelineJobs, NULL); |
| 1950 | + atomic_store(&state.newPipelines, NULL); |
1899 | 1951 | }
|
1900 | 1952 |
|
1901 | 1953 | lovrAssertGoto(fail, gpu_submit(streams, streamCount), "Failed to submit GPU command buffers: %s", gpu_get_error());
|
@@ -1924,7 +1976,7 @@ bool lovrGraphicsSubmit(Pass** passes, uint32_t count) {
|
1924 | 1976 | return true;
|
1925 | 1977 | fail:
|
1926 | 1978 | stackPop(&thread.stack, stack);
|
1927 |
| - atomic_store(&state.pipelineJobs, NULL); |
| 1979 | + atomic_store(&state.newPipelines, NULL); |
1928 | 1980 | return false;
|
1929 | 1981 | }
|
1930 | 1982 |
|
|
0 commit comments