Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
askmeaboutlo0m committed Aug 14, 2023
1 parent 84507f9 commit dbfbadd
Show file tree
Hide file tree
Showing 3 changed files with 400 additions and 0 deletions.
2 changes: 2 additions & 0 deletions src/drawdance/libcommon/dpcommon/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -268,9 +268,11 @@ DP_INLINE size_t DP_flex_size(size_t type_size, size_t flex_offset,
* member. Takes potential trailing padding being used as part of the flexible
* array member into account.
*/
// NOLINTBEGIN(bugprone-sizeof-expression)
#define DP_FLEX_SIZEOF(TYPE, FIELD, COUNT) \
DP_flex_size(sizeof(TYPE), offsetof(TYPE, FIELD), \
sizeof(((TYPE *)NULL)->FIELD[0]), COUNT)
// NOLINTEND(bugprone-sizeof-expression)


void *DP_malloc(size_t size) DP_MALLOC_ATTR;
Expand Down
378 changes: 378 additions & 0 deletions src/drawdance/libengine/dpengine/renderer.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,378 @@
#include "renderer.h"
#include "canvas_diff.h"
#include "canvas_state.h"
#include "layer_content.h"
#include "pixels.h"
#include "tile.h"
#include "view_mode.h"
#include <dpcommon/common.h>
#include <dpcommon/conversions.h>
#include <dpcommon/queue.h>
#include <dpcommon/threading.h>
#include <dpmsg/blend_mode.h>

#define TILE_QUEUE_INITIAL_CAPACITY 1024

typedef struct DP_RenderContext {
DP_ALIGNAS_SIMD DP_Pixel8 pixels[DP_TILE_LENGTH];
DP_ViewModeBuffer *vmb;
} DP_RenderContext;

typedef enum DP_RenderJobType {
DP_RENDER_JOB_TILE,
DP_RENDER_JOB_RESIZE,
DP_RENDER_JOB_WAIT,
DP_RENDER_JOB_INVALID,
DP_RENDER_JOB_QUIT,
} DP_RenderJobType;

typedef struct DP_RendererTileCoords {
int tile_x, tile_y;
} DP_RendererTileCoords;

typedef struct DP_RendererTileJob {
int tile_x, tile_y;
DP_CanvasState *cs;
} DP_RendererTileJob;

typedef struct DP_RendererResizeJob {
int offset_x, offset_y;
int prev_width, prev_height;
DP_PaintEngineResizedFn resized;
void *user;
} DP_RendererResizeJob;

typedef struct DP_RenderJob {
DP_RenderJobType type;
union {
int dummy; // Shut up overeager compiler warnings about initialization.
DP_RendererTileJob tile;
DP_RendererResizeJob resize;
};
} DP_RenderJob;

struct DP_Renderer {
DP_RenderContext *contexts;
DP_Queue resize_queue;
DP_Queue tile_queue;
DP_CanvasState *cs;
DP_Mutex *queue_mutex;
DP_Semaphore *queue_sem;
DP_Semaphore *wait_ready_sem;
DP_Semaphore *wait_done_sem;
int thread_count;
DP_Thread *threads[];
};


static DP_TransientTile *flatten_tile(DP_CanvasState *cs,
DP_ViewModeBuffer *vmb,
bool needs_checkers, int tile_index)
{
DP_TransientTile *tt = DP_transient_tile_new_nullable(
DP_canvas_state_background_tile_noinc(cs), 0);
DP_ViewModeFilter vmf =
DP_local_state_view_mode_filter_make(pe->local_state, vmb, cs);
DP_canvas_state_flatten_tile_to(cs, tile_index, tt, true, &vmf);

if (needs_checkers) {
DP_transient_tile_merge(tt, pe->checker, DP_BIT15,
DP_BLEND_MODE_BEHIND);
}
DP_transient_layer_content_transient_tile_set_noinc(pe->tlc, tt,
tile_index);
return tt;
}

static void render_job(void *user, int thread_index)
{
struct DP_PaintEngineRenderJobParams *job_params = user;
struct DP_PaintEngineRenderParams *render_params =
job_params->render_params;
int x = job_params->x;
int y = job_params->y;

DP_PaintEngine *pe = render_params->pe;
int tile_index = y * render_params->xtiles + x;
DP_TransientTile *tt =
flatten_tile(pe, pe->render.buffers[thread_index].vmb,
render_params->needs_checkers, tile_index);

DP_Pixel8 *pixel_buffer = pe->render.buffers[thread_index].pixels;
DP_pixels15_to_8_tile(pixel_buffer, DP_transient_tile_pixels(tt));

render_params->render_tile(render_params->user, x, y, pixel_buffer,
thread_index);

DP_SEMAPHORE_MUST_POST(pe->render.tiles_done_sem);
}

static void handle_tile_job(DP_Renderer *renderer, DP_RenderContext *rc,
DP_RendererTileJob *job)
{
(void)renderer;
(void)rc;
(void)job;

DP_TransientTile *tt =
flatten_tile(pe, rc->vmb, render_params->needs_checkers, tile_index);
}


static void handle_resize_job(DP_Renderer *renderer, DP_RendererResizeJob *job)
{
// Wait for all threads to be in a safe state before proceeding.
int wait_thread_count = renderer->thread_count - 1;
DP_SEMAPHORE_MUST_WAIT_N(renderer->wait_ready_sem, wait_thread_count);

job->resized(job->user, job->offset_x, job->offset_y, job->prev_width,
job->prev_height);

// Unlock the other threads.
DP_SEMAPHORE_MUST_POST_N(renderer->wait_done_sem, wait_thread_count);
}


static void handle_wait_job(DP_Renderer *renderer)
{
DP_SEMAPHORE_MUST_POST(renderer->wait_ready_sem);
DP_SEMAPHORE_MUST_WAIT(renderer->wait_done_sem);
}


static DP_RenderJob dequeue_job(DP_Renderer *renderer)
{
DP_Queue *resize_queue = &renderer->resize_queue;
DP_RenderJob *resize_job = DP_queue_peek(resize_queue, sizeof(*resize_job));
if (resize_job) {
DP_RenderJob job = *resize_job;
DP_queue_shift(resize_queue);
return job;
}

DP_Queue *tile_queue = &renderer->tile_queue;
DP_RendererTileCoords *coords = DP_queue_peek(tile_queue, sizeof(*coords));
if (coords) {
DP_RenderJob job;
if (coords->tile_x >= 0) {
job.type = DP_RENDER_JOB_TILE;
job.tile = (DP_RendererTileJob){
coords->tile_x,
coords->tile_y,
DP_canvas_state_incref(renderer->cs),
};
}
else {
job.type = DP_RENDER_JOB_INVALID;
}
DP_queue_shift(tile_queue);
return job;
}

return (DP_RenderJob){DP_RENDER_JOB_QUIT, {0}};
}

static void handle_jobs(DP_Renderer *renderer, int thread_index)
{
DP_Mutex *queue_mutex = renderer->queue_mutex;
DP_Semaphore *queue_sem = renderer->queue_sem;
DP_RenderContext *rc = &renderer->contexts[thread_index];
while (true) {
DP_SEMAPHORE_MUST_WAIT(queue_sem);
DP_MUTEX_MUST_LOCK(queue_mutex);
DP_RenderJob job = dequeue_job(renderer);
DP_MUTEX_MUST_UNLOCK(queue_mutex);
switch (job.type) {
case DP_RENDER_JOB_TILE:
handle_tile_job(renderer, rc, &job.tile);
break;
case DP_RENDER_JOB_RESIZE:
handle_resize_job(renderer, &job.resize);
break;
case DP_RENDER_JOB_WAIT:
handle_wait_job(renderer);
break;
case DP_RENDER_JOB_INVALID:
break;
case DP_RENDER_JOB_QUIT:
return;
default:
DP_UNREACHABLE();
}
}
}

struct DP_RenderWorkerParams {
DP_Renderer *renderer;
int thread_index;
};

static void run_worker_thread(void *user)
{
struct DP_RenderWorkerParams *params = user;
DP_Renderer *renderer = params->renderer;
int thread_index = params->thread_index;
DP_free(params);
handle_jobs(renderer, thread_index);
}


DP_Renderer *DP_renderer_new(int thread_count)
{
DP_ASSERT(thread_count > 0);
size_t size_thread_count = DP_int_to_size(thread_count);
DP_Renderer *renderer =
DP_malloc(DP_FLEX_SIZEOF(DP_Renderer, threads, size_thread_count));
renderer->thread_count = thread_count;
renderer->queue_mutex = NULL;
renderer->queue_sem = NULL;
renderer->wait_ready_sem = NULL;
renderer->wait_done_sem = NULL;
DP_queue_init(&renderer->resize_queue, size_thread_count * 2,
sizeof(DP_RenderJob));
DP_queue_init(&renderer->tile_queue, TILE_QUEUE_INITIAL_CAPACITY,
sizeof(DP_RendererTileCoords));
renderer->cs = DP_canvas_state_new();
renderer->contexts = DP_malloc_simd(sizeof(*renderer->contexts)
* DP_int_to_size(thread_count));
for (int i = 0; i < thread_count; ++i) {
renderer->contexts[i].vmb = DP_view_mode_buffer_new();
renderer->threads[i] = NULL;
}

#define RENDERER_ENSURE(X) \
do { \
if (!(X)) { \
DP_renderer_free(renderer); \
return NULL; \
} \
} while (0)

RENDERER_ENSURE(renderer->queue_mutex = DP_mutex_new());
RENDERER_ENSURE(renderer->queue_sem = DP_semaphore_new(0));
RENDERER_ENSURE(renderer->wait_ready_sem = DP_semaphore_new(0));
RENDERER_ENSURE(renderer->wait_done_sem = DP_semaphore_new(0));

#undef RENDERER_ENSURE

for (int i = 0; i < thread_count; ++i) {
struct DP_RenderWorkerParams *params = DP_malloc(sizeof(*params));
*params = (struct DP_RenderWorkerParams){renderer, i};
renderer->threads[i] = DP_thread_new(run_worker_thread, params);
if (!renderer->threads[i]) {
DP_free(params);
DP_renderer_free(renderer);
return NULL;
}
}

return renderer;
}

void DP_renderer_free(DP_Renderer *renderer)
{
if (renderer) {
int thread_count = renderer->thread_count;
DP_MUTEX_MUST_LOCK(renderer->queue_mutex);
renderer->resize_queue.used = 0;
renderer->tile_queue.used = 0;
DP_SEMAPHORE_MUST_POST_N(renderer->queue_sem, thread_count);
DP_MUTEX_MUST_UNLOCK(renderer->queue_mutex);

for (int i = 0; i < thread_count; ++i) {
DP_thread_free_join(renderer->threads[i]);
}
DP_semaphore_free(renderer->wait_done_sem);
DP_semaphore_free(renderer->wait_ready_sem);
DP_semaphore_free(renderer->queue_sem);
DP_mutex_free(renderer->queue_mutex);
DP_canvas_state_decref(renderer->cs);
DP_queue_dispose(&renderer->tile_queue);
DP_queue_dispose(&renderer->resize_queue);
for (int i = 0; i < thread_count; ++i) {
DP_view_mode_buffer_free(renderer->contexts[i].vmb);
}
DP_free_simd(renderer->contexts);
DP_free(renderer);
}
}


static void invalidate_tile_coords(void *element, DP_UNUSED void *user)
{
DP_RendererTileCoords *coords = element;
coords->tile_x = -1;
}

void DP_renderer_resize(DP_Renderer *renderer, int offset_x, int offset_y,
int prev_width, int prev_height,
DP_PaintEngineResizedFn resized, void *user)
{
DP_ASSERT(renderer);
DP_ASSERT(resized);

DP_Mutex *queue_mutex = renderer->queue_mutex;
DP_Queue *resize_queue = &renderer->resize_queue;
DP_Queue *tile_queue = &renderer->tile_queue;
int thread_count = renderer->thread_count;
int wait_thread_count = renderer->thread_count - 1;
DP_MUTEX_MUST_LOCK(queue_mutex);

// Resizing must be synchronized between all render threads, since all tile
// positions are invalidated by the operation. So we enqueue a wait job for
// all except one thread, plus the actual resize job for the last one.
for (int i = 0; i < wait_thread_count; ++i) {
DP_RenderJob *wait_job = DP_queue_push(resize_queue, sizeof(*wait_job));
wait_job->type = DP_RENDER_JOB_WAIT;
}

DP_RenderJob *resize_job = DP_queue_push(resize_queue, sizeof(*resize_job));
resize_job->type = DP_RENDER_JOB_RESIZE;
resize_job->resize = (DP_RendererResizeJob){
offset_x, offset_y, prev_width, prev_height, resized, user};

DP_SEMAPHORE_MUST_POST_N(renderer->queue_sem, thread_count);

// All current tile jobs are invalidated by the resize, so we turn those
// into tombstones to avoid any pointless processing thereof.
DP_queue_each(tile_queue, sizeof(DP_RendererTileCoords),
invalidate_tile_coords, NULL);

DP_MUTEX_MUST_UNLOCK(queue_mutex);
}


struct DP_RendererPushTileParams {
DP_Queue *tile_queue;
int pushed;
};

static void push_tile(void *user, int tile_x, int tile_y)
{
struct DP_RendererPushTileParams *params = user;
// TODO: deduplicate to avoid re-rendering the same tile.
DP_RendererTileCoords *coords =
DP_queue_push(params->tile_queue, sizeof(*coords));
*coords = (DP_RendererTileCoords){tile_x, tile_y};
++params->pushed;
}

void DP_renderer_apply(DP_Renderer *renderer, DP_CanvasState *cs,
DP_CanvasDiff *diff)
{
DP_ASSERT(renderer);
DP_ASSERT(cs);
DP_ASSERT(diff);

DP_Mutex *queue_mutex = renderer->queue_mutex;
struct DP_RendererPushTileParams params = {&renderer->tile_queue, 0};
DP_MUTEX_MUST_LOCK(queue_mutex);

DP_canvas_state_decref(renderer->cs);
renderer->cs = DP_canvas_state_incref(cs);

DP_canvas_diff_each_pos_reset(diff, push_tile, &params);
DP_SEMAPHORE_MUST_POST_N(renderer->queue_sem, params.pushed);

DP_MUTEX_MUST_UNLOCK(queue_mutex);
}
Loading

0 comments on commit dbfbadd

Please sign in to comment.