Skip to content

Commit

Permalink
Fix & simplify render cluster state updates
Browse files Browse the repository at this point in the history
It was previously just broken, and overcomplicated. I think it was
trying to somehow aggregate performance metrics on the worker nodes, and
then the master node was supposed to work out performance figures from
that, but that was way more complicated than it need be.
Now we just sync the tile states, and let the existing performance
metrics/state monitoring logic handle the rest.
Also cleaned up some of the SDL UI code, as that was also overbuilt.

Starting a see a pattern here - Slightly younger me really liked to come
up with really complicated solutions to problems :^)
  • Loading branch information
vkoskiv committed Nov 3, 2023
1 parent 6e6577e commit 374ab0a
Show file tree
Hide file tree
Showing 7 changed files with 86 additions and 93 deletions.
5 changes: 3 additions & 2 deletions src/datatypes/tile.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ enum renderOrder {
struct renderer;

enum tile_state {
ready_to_render,
ready_to_render = 0,
rendering,
finished
};
Expand All @@ -37,8 +37,9 @@ struct renderTile {
struct intCoord begin;
struct intCoord end;
enum tile_state state;
bool networkRenderer;
bool networkRenderer; //FIXME: client struct ptr
int tileNum;
size_t completed_samples;
};

/// Quantize the render plane into an array of tiles, with properties as specified in the parameters below
Expand Down
1 change: 1 addition & 0 deletions src/renderer/renderer.c
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,7 @@ void *renderThread(void *arg) {
totalUsec += timer_get_us(timer);
threadState->totalSamples++;
threadState->completedSamples++;
tile->completed_samples++;
//Pause rendering when bool is set
while (threadState->paused && !r->state.render_aborted) {
timer_sleep_ms(100);
Expand Down
3 changes: 1 addition & 2 deletions src/renderer/renderer.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,7 @@ struct worker {

//Share info about the current tile with main thread
struct renderTile *currentTile;
size_t completedSamples;

size_t completedSamples; //FIXME: Remove
uint64_t totalSamples;

long avgSampleTime; //Single tile pass
Expand Down
4 changes: 4 additions & 0 deletions src/utils/protocol/protocol.c
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,9 @@ cJSON *encodeTile(const struct renderTile *tile) {
cJSON_AddNumberToObject(json, "beginY", tile->begin.y);
cJSON_AddNumberToObject(json, "endX", tile->end.x);
cJSON_AddNumberToObject(json, "endY", tile->end.y);
cJSON_AddNumberToObject(json, "state", tile->state);
cJSON_AddNumberToObject(json, "tileNum", tile->tileNum);
cJSON_AddNumberToObject(json, "completed_samples", tile->completed_samples);
return json;
}

Expand All @@ -91,7 +93,9 @@ struct renderTile decodeTile(const cJSON *json) {
tile.begin.y = cJSON_GetObjectItem(json, "beginY")->valueint;
tile.end.x = cJSON_GetObjectItem(json, "endX")->valueint;
tile.end.y = cJSON_GetObjectItem(json, "endY")->valueint;
tile.state = cJSON_GetObjectItem(json, "state")->valueint;
tile.tileNum = cJSON_GetObjectItem(json, "tileNum")->valueint;
tile.completed_samples = cJSON_GetObjectItem(json, "completed_samples")->valueint;
return tile;
}

Expand Down
16 changes: 11 additions & 5 deletions src/utils/protocol/server.c
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,8 @@ static cJSON *processSubmitWork(struct worker *state, const cJSON *json) {
struct texture *tileImage = decodeTexture(resultJson);
cJSON *tileJson = cJSON_GetObjectItem(json, "tile");
struct renderTile tile = decodeTile(tileJson);
state->renderer->state.renderTiles[tile.tileNum].state = finished;
state->renderer->state.renderTiles[tile.tileNum] = tile;
state->renderer->state.renderTiles[tile.tileNum].state = finished; // FIXME: Remove
for (int y = tile.end.y - 1; y > tile.begin.y - 1; --y) {
for (int x = tile.begin.x; x < tile.end.x; ++x) {
struct color value = textureGetPixel(tileImage, x - tile.begin.x, y - tile.begin.y, false);
Expand Down Expand Up @@ -242,10 +243,15 @@ void *networkRenderThread(void *arg) {
while (r->state.rendering && !state->thread_complete) {
cJSON *request = readJSON(client->socket);
if (containsStats(request)) {
cJSON *completed = cJSON_GetObjectItem(request, "completed");
if (cJSON_IsNumber(completed)) state->completedSamples = completed->valueint;
cJSON *avg = cJSON_GetObjectItem(request, "avgPerPass");
if (cJSON_IsNumber(avg)) state->avgSampleTime = avg->valuedouble;
cJSON *array = cJSON_GetObjectItem(request, "tiles");
if (cJSON_IsArray(array)) {
cJSON *tile = NULL;
cJSON_ArrayForEach(tile, array) {
struct renderTile t = decodeTile(tile);
r->state.renderTiles[t.tileNum] = t;
//r->state.renderTiles[t.tileNum].completed_samples = t.completed_samples;
}
}
} else {
cJSON *response = processClientRequest(state, request);
if (containsError(response)) {
Expand Down
105 changes: 48 additions & 57 deletions src/utils/protocol/worker.c
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,9 @@ struct workerThreadState {
struct camera *cam;
struct renderer *renderer;
bool threadComplete;
uint64_t totalSamples;
size_t completedSamples;
long avgSampleTime;
struct renderTile *current;
};

static cJSON *validateHandshake(const cJSON *in) {
Expand Down Expand Up @@ -99,81 +99,83 @@ static cJSON *receiveScene(const cJSON *json) {
}

// Tilenum of -1 communicates that it failed to get work, signaling the work thread to exit
static struct renderTile getWork(int connectionSocket) {
static struct renderTile *getWork(int connectionSocket) {
if (!sendJSON(connectionSocket, newAction("getWork"), NULL)) {
return (struct renderTile){ .tileNum = -1 };
return NULL;
}
cJSON *response = readJSON(connectionSocket);
if (!response) return NULL;
cJSON *action = cJSON_GetObjectItem(response, "action");
if (cJSON_IsString(action)) {
if (stringEquals(action->valuestring, "renderComplete")) {
logr(debug, "Master reported render is complete\n");
return (struct renderTile){ .tileNum = -1 };
return NULL;
}
}
if (!response) {
return (struct renderTile){ .tileNum = -1 };
}
// FIXME: Pass the tile index only, and rename it to index too.
// In fact, this whole tile object thing might be a bit pointless, since
// we can just keep track of indices, and compute the tile dims
cJSON *tileJson = cJSON_GetObjectItem(response, "tile");
struct renderTile tile = decodeTile(tileJson);
g_worker_renderer->state.renderTiles[tile.tileNum] = tile;
logr(debug, "Got work : %i ((%i,%i),(%i,%i))\n", tile.tileNum, tile.begin.x, tile.begin.y, tile.end.x, tile.end.y);
cJSON_Delete(response);
return tile;
return &g_worker_renderer->state.renderTiles[tile.tileNum];
}

static bool submitWork(int sock, struct texture *work, struct renderTile forTile) {
static bool submitWork(int sock, struct texture *work, struct renderTile *forTile) {
cJSON *result = encodeTexture(work);
cJSON *tile = encodeTile(&forTile);
cJSON *tile = encodeTile(forTile);
cJSON *package = newAction("submitWork");
cJSON_AddItemToObject(package, "result", result);
cJSON_AddItemToObject(package, "tile", tile);
logr(debug, "Submit work: %i\n", forTile.tileNum);
logr(debug, "Submit work: %i\n", forTile->tileNum);
return sendJSON(sock, package, NULL);
}

static void *workerThread(void *arg) {
block_signals();
struct workerThreadState *threadState = (struct workerThreadState *)thread_user_data(arg);
struct renderer *r = threadState->renderer;
int sock = threadState->connectionSocket;
struct cr_mutex *sockMutex = threadState->socketMutex;
struct workerThreadState *thread = (struct workerThreadState *)thread_user_data(arg);
struct renderer *r = thread->renderer;
int sock = thread->connectionSocket;
struct cr_mutex *sockMutex = thread->socketMutex;

//Fetch initial task
mutex_lock(sockMutex);
struct renderTile tile = getWork(sock);
thread->current = getWork(sock);
mutex_release(sockMutex);
struct texture *tileBuffer = newTexture(char_p, tile.width, tile.height, 3);
struct texture *tileBuffer = newTexture(char_p, thread->current->width, thread->current->height, 3);
sampler *sampler = newSampler();

struct camera *cam = threadState->cam;
struct camera *cam = thread->cam;

struct timeval timer = { 0 };
threadState->completedSamples = 1;
thread->completedSamples = 1;

while (tile.tileNum != -1 && r->state.rendering) {
if (tileBuffer->width != tile.width || tileBuffer->height != tile.height) {
while (thread->current && r->state.rendering) {
if (tileBuffer->width != thread->current->width || tileBuffer->height != thread->current->height) {
destroyTexture(tileBuffer);
tileBuffer = newTexture(char_p, tile.width, tile.height, 3);
tileBuffer = newTexture(char_p, thread->current->width, thread->current->height, 3);
}
long totalUsec = 0;
long samples = 0;

while (threadState->completedSamples < r->prefs.sampleCount+1 && r->state.rendering) {
while (thread->completedSamples < r->prefs.sampleCount+1 && r->state.rendering) {
timer_start(&timer);
for (int y = tile.end.y - 1; y > tile.begin.y - 1; --y) {
for (int x = tile.begin.x; x < tile.end.x; ++x) {
for (int y = thread->current->end.y - 1; y > thread->current->begin.y - 1; --y) {
for (int x = thread->current->begin.x; x < thread->current->end.x; ++x) {
if (r->state.render_aborted || !g_running) goto bail;
uint32_t pixIdx = (uint32_t)(y * cam->width + x);
initSampler(sampler, SAMPLING_STRATEGY, threadState->completedSamples - 1, r->prefs.sampleCount, pixIdx);
initSampler(sampler, SAMPLING_STRATEGY, thread->completedSamples - 1, r->prefs.sampleCount, pixIdx);

struct color output = textureGetPixel(r->state.renderBuffer, x, y, false);
struct lightRay incidentRay = cam_get_ray(cam, x, y, sampler);
struct color sample = path_trace(&incidentRay, r->scene, r->prefs.bounces, sampler);

//And process the running average
output = colorCoef((float)(threadState->completedSamples - 1), output);
output = colorCoef((float)(thread->completedSamples - 1), output);
output = colorAdd(output, sample);
float t = 1.0f / threadState->completedSamples;
float t = 1.0f / thread->completedSamples;
output = colorCoef(t, output);

//Store internal render buffer (float precision)
Expand All @@ -183,21 +185,22 @@ static void *workerThread(void *arg) {
output = colorToSRGB(output);

//And store the image data
int localX = x - tile.begin.x;
int localY = y - tile.begin.y;
int localX = x - thread->current->begin.x;
int localY = y - thread->current->begin.y;
setPixel(tileBuffer, output, localX, localY);
}
}
//For performance metrics
samples++;
totalUsec += timer_get_us(timer);
threadState->totalSamples++;
threadState->completedSamples++;
threadState->avgSampleTime = totalUsec / samples;
thread->completedSamples++;
thread->current->completed_samples++;
thread->avgSampleTime = totalUsec / samples;
}

thread->current->state = finished;
mutex_lock(sockMutex);
if (!submitWork(sock, tileBuffer, tile)) {
if (!submitWork(sock, tileBuffer, thread->current)) {
mutex_release(sockMutex);
break;
}
Expand All @@ -207,16 +210,16 @@ static void *workerThread(void *arg) {
break;
}
mutex_release(sockMutex);
threadState->completedSamples = 1;
thread->completedSamples = 1;
mutex_lock(sockMutex);
tile = getWork(sock);
thread->current = getWork(sock);
mutex_release(sockMutex);
}
bail:
destroySampler(sampler);
destroyTexture(tileBuffer);

threadState->threadComplete = true;
thread->threadComplete = true;
return 0;
}

Expand Down Expand Up @@ -248,31 +251,19 @@ static cJSON *startRender(int connectionSocket) {
}
}

float avgSampleTime = 0.0f;
float avgTimePerTilePass = 0.0f;
int ctr = 1;
int pauser = 0;
//TODO: Send out stats here
while (g_worker_renderer->state.rendering) {

// Gather and send statistics to master node
for (size_t t = 0; t < g_worker_renderer->prefs.threads; ++t) {
avgSampleTime += workerThreadStates[t].avgSampleTime;
}
avgTimePerTilePass += avgSampleTime / g_worker_renderer->prefs.threads;
avgTimePerTilePass /= ctr++;

// Send stats about 1x/s
if (pauser == 1024 / active_msec) {
uint64_t completedSamples = 0;
// Send stats about 4x/s
if (pauser == 256 / active_msec) {
cJSON *stats = newAction("stats");
cJSON *array = cJSON_AddArrayToObject(stats, "tiles");
for (size_t t = 0; t < threadCount; ++t) {
completedSamples += workerThreadStates[t].totalSamples;
struct renderTile *tile = workerThreadStates[t].current;
if (tile) cJSON_AddItemToArray(array, encodeTile(tile));
}
cJSON *stats = newAction("stats");
cJSON_AddNumberToObject(stats, "completed", completedSamples);
cJSON_AddNumberToObject(stats, "avgPerPass", (double)avgTimePerTilePass);

mutex_lock(g_worker_socket_mutex);
logr(debug, "Sending stats update for: %"PRIu64", %.2f\n", completedSamples, (double)avgTimePerTilePass);
logr(debug, "Sending stats update: %s\n", cJSON_Print(stats));
if (!sendJSON(connectionSocket, stats, NULL)) {
logr(debug, "Connection lost, bailing out.\n");
// Setting this flag also kills the threads.
Expand Down
45 changes: 18 additions & 27 deletions src/utils/ui.c
Original file line number Diff line number Diff line change
Expand Up @@ -304,11 +304,11 @@ void getKeyboardInput(struct renderer *r) {
}
}

static void clearProgBar(struct renderer *r, struct renderTile temp) {
for (unsigned i = 0; i < temp.width; ++i) {
setPixel(r->state.uiBuffer, g_clear_color, temp.begin.x + i, (temp.begin.y + (temp.height / 5)) - 1);
setPixel(r->state.uiBuffer, g_clear_color, temp.begin.x + i, (temp.begin.y + (temp.height / 5)) );
setPixel(r->state.uiBuffer, g_clear_color, temp.begin.x + i, (temp.begin.y + (temp.height / 5)) + 1);
static void clearProgBar(struct renderer *r, struct renderTile *t) {
for (unsigned i = 0; i < t->width; ++i) {
setPixel(r->state.uiBuffer, g_clear_color, t->begin.x + i, (t->begin.y + (t->height / 5)) - 1);
setPixel(r->state.uiBuffer, g_clear_color, t->begin.x + i, (t->begin.y + (t->height / 5)) );
setPixel(r->state.uiBuffer, g_clear_color, t->begin.x + i, (t->begin.y + (t->height / 5)) + 1);
}
}

Expand All @@ -320,31 +320,22 @@ static void clearProgBar(struct renderer *r, struct renderTile temp) {
around that.
*/
static void drawProgressBars(struct renderer *r) {
for (size_t t = 0; t < r->prefs.threads; ++t) {
if (r->state.workers[t].currentTile) {
struct renderTile *temp = r->state.workers[t].currentTile;
int completedSamples = r->state.workers[t].completedSamples;
int totalSamples = r->prefs.sampleCount;

float prc = ((float)completedSamples / (float)totalSamples);
int pixels2draw = (int)((float)temp->width * prc);

struct color c = temp->state == rendering ? g_prog_color: g_clear_color;

//And then draw the bar
for (int i = 0; i < pixels2draw; ++i) {
setPixel(r->state.uiBuffer, c, temp->begin.x + i, (temp->begin.y + (temp->height / 5)) - 1);
setPixel(r->state.uiBuffer, c, temp->begin.x + i, (temp->begin.y + (temp->height / 5)) );
setPixel(r->state.uiBuffer, c, temp->begin.x + i, (temp->begin.y + (temp->height / 5)) + 1);
for (size_t tile = 0; tile < r->state.tileCount; ++tile) {
struct renderTile *t = &r->state.renderTiles[tile];
float prc = ((float)t->completed_samples / r->prefs.sampleCount);
size_t pixels = (int)((float)t->width * prc);
struct color c = t->state == rendering ? g_prog_color : g_clear_color;
//And then draw the bar
if (t->state == finished) {
clearProgBar(r, t);
} else {
for (size_t i = 0; i < pixels; ++i) {
setPixel(r->state.uiBuffer, c, t->begin.x + i, (t->begin.y + (t->height / 5)) - 1);
setPixel(r->state.uiBuffer, c, t->begin.x + i, (t->begin.y + (t->height / 5)) );
setPixel(r->state.uiBuffer, c, t->begin.x + i, (t->begin.y + (t->height / 5)) + 1);
}
}
}
for (size_t i = 0; i < r->state.tileCount; ++i) {
if (r->state.renderTiles[i].state == finished) {
clearProgBar(r, r->state.renderTiles[i]);
}

}
}

/**
Expand Down

0 comments on commit 374ab0a

Please sign in to comment.