Skip to content

Commit 6922366

Browse files
committed
Compile slow pipelines on worker threads;
1 parent 5930639 commit 6922366

File tree

1 file changed

+120
-68
lines changed

1 file changed

+120
-68
lines changed

src/modules/graphics/graphics.c

Lines changed: 120 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include "headset/headset.h"
88
#include "math/math.h"
99
#include "core/gpu.h"
10+
#include "core/job.h"
1011
#include "core/maf.h"
1112
#include "core/spv.h"
1213
#include "core/os.h"
@@ -554,9 +555,11 @@ struct Pass {
554555

555556
typedef struct PipelineJob {
556557
struct PipelineJob* next;
558+
job* handle;
557559
uint64_t hash;
558560
gpu_pipeline_info* info;
559561
gpu_pipeline* pipeline;
562+
char* error;
560563
} PipelineJob;
561564

562565
static thread_local struct {
@@ -595,7 +598,7 @@ static struct {
595598
Readback* newestReadback;
596599
MaterialBlock* materials;
597600
BufferAllocator bufferAllocators[4];
598-
PipelineJob* pipelineJobs;
601+
PipelineJob* newPipelines;
599602
map_t pipelineLookup;
600603
gpu_pipeline* pipelines;
601604
uint32_t pipelineCount;
@@ -1120,13 +1123,100 @@ static bool recordComputePass(Pass* pass, gpu_stream* stream) {
11201123
return true;
11211124
}
11221125

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+
11231136
static bool recordRenderPass(Pass* pass, gpu_stream* stream) {
11241137
Canvas* canvas = &pass->canvas;
11251138

11261139
if (!canvas->color->texture && !canvas->depth.texture) {
11271140
return true;
11281141
}
11291142

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+
11301220
// Render area
11311221

11321222
uint32_t min[2] = { ~0u, ~0u };
@@ -1315,70 +1405,6 @@ static bool recordRenderPass(Pass* pass, gpu_stream* stream) {
13151405
gpu_bundle* builtinBundle = getBundle(state.builtinLayout, builtins, COUNTOF(builtins));
13161406
if (!builtinBundle) return false;
13171407

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-
13821408
// Bundles
13831409

13841410
Draw* prev = NULL;
@@ -1451,6 +1477,32 @@ static bool recordRenderPass(Pass* pass, gpu_stream* stream) {
14511477
gpu_render_begin(stream, &pass->target);
14521478
gpu_bind_vertex_buffers(stream, &state.defaultBuffer->gpu, &state.defaultBuffer->base, 1, 1);
14531479

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+
14541506
for (uint32_t i = 0; i < activeDrawCount; i++) {
14551507
Draw* draw = &pass->draws[activeDraws[i]];
14561508

@@ -1889,13 +1941,13 @@ bool lovrGraphicsSubmit(Pass** passes, uint32_t count) {
18891941
}
18901942

18911943
// 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) {
18931945
if (map_get(&state.pipelineLookup, job->hash) == MAP_NIL) {
18941946
map_set(&state.pipelineLookup, job->hash, (uint64_t) (uintptr_t) job->pipeline);
18951947
}
18961948
}
18971949

1898-
atomic_store(&state.pipelineJobs, NULL);
1950+
atomic_store(&state.newPipelines, NULL);
18991951
}
19001952

19011953
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) {
19241976
return true;
19251977
fail:
19261978
stackPop(&thread.stack, stack);
1927-
atomic_store(&state.pipelineJobs, NULL);
1979+
atomic_store(&state.newPipelines, NULL);
19281980
return false;
19291981
}
19301982

0 commit comments

Comments
 (0)