-
Notifications
You must be signed in to change notification settings - Fork 133
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
84507f9
commit dbfbadd
Showing
3 changed files
with
400 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, ¶ms); | ||
DP_SEMAPHORE_MUST_POST_N(renderer->queue_sem, params.pushed); | ||
|
||
DP_MUTEX_MUST_UNLOCK(queue_mutex); | ||
} |
Oops, something went wrong.