diff --git a/src/develop/develop.c b/src/develop/develop.c index eef9b34d3596..53dd3d78d1f5 100644 --- a/src/develop/develop.c +++ b/src/develop/develop.c @@ -274,7 +274,7 @@ void dt_dev_cleanup(dt_develop_t *dev) void dt_dev_process_image(dt_develop_t *dev) { - if(!dev->gui_attached || dev->full.pipe->processing) return; + if(!dev->gui_attached || dt_pipe_started(dev->full.pipe)) return; const gboolean err = dt_control_add_job_res(dt_dev_process_image_job_create(dev), DT_CTL_WORKER_ZOOM_1); if(err) dt_print(DT_DEBUG_ALWAYS, "[dev_process_image] job queue exceeded!"); } @@ -613,7 +613,7 @@ void dt_dev_process_image_job(dt_develop_t *dev, dt_pthread_mutex_lock(&pipe->mutex); - if(dev->gui_leaving || (dt_atomic_get_int(&pipe->shutdown) != DT_DEV_PIXELPIPE_STOP_NO)) + if(dev->gui_leaving || dt_pipe_started(pipe)) { dt_pthread_mutex_unlock(&pipe->mutex); return; @@ -778,7 +778,7 @@ void dt_dev_process_image_job(dt_develop_t *dev, const gboolean early = dt_dev_pixelpipe_process(pipe, dev, x, y, wd, ht, scale, devid); const dt_dev_pixelpipe_stopper_t shutdown = dt_atomic_exch_int(&pipe->shutdown, DT_DEV_PIXELPIPE_STOP_NO); - const gboolean stopped = early || shutdown != DT_DEV_PIXELPIPE_STOP_NO; + const gboolean stopped = early || shutdown > DT_DEV_PIXELPIPE_PROCESSING; const dt_iop_roi_t proi = (dt_iop_roi_t) {.x = x, .y = y, .width = wd, .height = ht, .scale = scale }; dt_print_pipe(DT_DEBUG_PIPE, stopped ? "pixelpipe_process stopped" : "pixelpipe_process good", @@ -813,7 +813,7 @@ void dt_dev_process_image_job(dt_develop_t *dev, /* pixelpipe stopped due to changed pipe nodes, HQ mode changes or module aborts while processing the pipe. All require restarts as pipe status is not valid yet. */ - if(shutdown != DT_DEV_PIXELPIPE_STOP_NO) + if(shutdown > DT_DEV_PIXELPIPE_PROCESSING) { dt_print_pipe(DT_DEBUG_PIPE | DT_DEBUG_VERBOSE, "image_job restart", pipe, NULL, DT_DEVICE_NONE, &proi, NULL); goto restart; @@ -2799,7 +2799,6 @@ void dt_dev_reprocess_center(dt_develop_t *dev) if(dev && dev->gui_attached) { dev->full.pipe->changed |= DT_DEV_PIPE_SYNCH; - dev->full.pipe->cache_obsolete = TRUE; // invalidate buffers and force redraw of darkroom dt_dev_invalidate_all(dev); @@ -2815,7 +2814,6 @@ void dt_dev_reprocess_preview(dt_develop_t *dev) return; dev->preview_pipe->changed |= DT_DEV_PIPE_SYNCH; - dev->preview_pipe->cache_obsolete = TRUE; dt_dev_invalidate_preview(dev); dt_control_queue_redraw_center(); @@ -3268,7 +3266,11 @@ void dt_dev_zoom_move(dt_dev_viewport_t *port, port->pipe->changed |= DT_DEV_PIPE_ZOOMED; if(port->widget) + { + if(dt_pipe_processing(port->pipe)) + dt_dev_pixelpipe_set_shutdown(port->pipe, DT_DEV_PIXELPIPE_STOP_ZOOM); dt_control_queue_redraw_widget(port->widget); + } if(port == &dev->full) dt_control_navigation_redraw(); } @@ -3793,7 +3795,7 @@ static gboolean _dev_wait_hash(dt_develop_t *dev, for(int n = 0; n < nloop; n++) { - if(dt_atomic_get_int(&pipe->shutdown) != DT_DEV_PIXELPIPE_STOP_NO) + if(dt_atomic_get_int(&pipe->shutdown) > DT_DEV_PIXELPIPE_PROCESSING) return TRUE; // stop waiting if pipe shuts down dt_hash_t probehash; diff --git a/src/develop/pixelpipe_cache.c b/src/develop/pixelpipe_cache.c index 80923dd4473c..beee9b5f131d 100644 --- a/src/develop/pixelpipe_cache.c +++ b/src/develop/pixelpipe_cache.c @@ -388,17 +388,32 @@ void dt_dev_pixelpipe_cache_invalidate_later(dt_dev_pixelpipe_t *pipe, invalidated++; } } + pipe->bcache_hash = DT_INVALID_HASH; + + if(invalidated) + dt_print_pipe(DT_DEBUG_PIPE, "pipecache invalidate", pipe, NULL, DT_DEVICE_NONE, NULL, NULL, + "%s%i cachelines after ioporder=%i", info ? info : "", invalidated, order); +} - const gboolean bcache = pipe->bcache_data != NULL && pipe->bcache_hash != DT_INVALID_HASH; +void dt_dev_pixelpipe_cache_invalidate_iop(dt_dev_pixelpipe_t *pipe, + const int32_t order, + const char *info) +{ + const dt_dev_pixelpipe_cache_t *cache = &pipe->cache; + int invalidated = 0; + for(int k = DT_PIPECACHE_MIN; k < cache->entries; k++) + { + if((cache->ioporder[k] == order) && (cache->hash[k] != DT_INVALID_HASH)) + { + _mark_invalid_cacheline(cache, k); + invalidated++; + } + } pipe->bcache_hash = DT_INVALID_HASH; - if(invalidated || bcache) - dt_print_pipe(DT_DEBUG_PIPE, - order ? "pipecache invalidate" : "pipecache flush", - pipe, NULL, DT_DEVICE_NONE, NULL, NULL, - "%s%i cachelines after ioporder=%i%s", - info ? info : "", - invalidated, order, bcache ? ", blend cache" : ""); + if(invalidated) + dt_print_pipe(DT_DEBUG_PIPE, "pipecache invalidate", pipe, NULL, DT_DEVICE_NONE, NULL, NULL, + "%s%i cachelines for ioporder=%i", info ? info : "", invalidated, order); } void dt_dev_pixelpipe_cache_flush(dt_dev_pixelpipe_t *pipe) diff --git a/src/develop/pixelpipe_cache.h b/src/develop/pixelpipe_cache.h index d91ed4c8d2c8..5cf5a0974a76 100644 --- a/src/develop/pixelpipe_cache.h +++ b/src/develop/pixelpipe_cache.h @@ -86,6 +86,9 @@ void dt_dev_pixelpipe_cache_flush(struct dt_dev_pixelpipe_t *pipe); /** invalidates all cachelines for modules with at least the same iop_order */ void dt_dev_pixelpipe_cache_invalidate_later(struct dt_dev_pixelpipe_t *pipe, const int32_t order, const char *info); +/** invalidates all cachelines marked with the given iop_order */ +void dt_dev_pixelpipe_cache_invalidate_iop(struct dt_dev_pixelpipe_t *pipe, const int32_t order, const char *info); + /** makes this buffer very important after it has been pulled from the cache. */ void dt_dev_pixelpipe_important_cacheline(const struct dt_dev_pixelpipe_t *pipe, const void *data, const size_t size); diff --git a/src/develop/pixelpipe_hb.c b/src/develop/pixelpipe_hb.c index d6f1d1bb3348..249c4c131af7 100644 --- a/src/develop/pixelpipe_hb.c +++ b/src/develop/pixelpipe_hb.c @@ -49,7 +49,8 @@ static inline gboolean _pipe_has_shutdown(dt_dev_pixelpipe_t *pipe) { - return dt_atomic_get_int(&pipe->shutdown) != DT_DEV_PIXELPIPE_STOP_NO; + const dt_dev_pixelpipe_stopper_t stopper = dt_atomic_get_int(&pipe->shutdown); + return stopper > DT_DEV_PIXELPIPE_PROCESSING && stopper != DT_DEV_PIXELPIPE_STOP_ZOOM; } // benchmarking and pfm dumps should happen for these pipes if there is no shutdown request @@ -106,8 +107,10 @@ const char *dt_dev_pixelpipe_shutdown_to_str(const dt_dev_pixelpipe_stopper_t st switch(stopper) { case DT_DEV_PIXELPIPE_STOP_NO: return "DT_DEV_PIXELPIPE_STOP_NO"; + case DT_DEV_PIXELPIPE_PROCESSING: return "DT_DEV_PIXELPIPE_PROCESSING"; case DT_DEV_PIXELPIPE_STOP_NODES: return "DT_DEV_PIXELPIPE_STOP_NODES"; case DT_DEV_PIXELPIPE_STOP_HQ: return "DT_DEV_PIXELPIPE_STOP_HQ"; + case DT_DEV_PIXELPIPE_STOP_ZOOM: return "DT_DEV_PIXELPIPE_STOP_ZOOM"; case DT_DEV_PIXELPIPE_STOP_LAST: return "DT_DEV_PIXELPIPE_STOP_LAST"; default: return "DT_DEV_PIXELPIPE_STOP_MODULE"; } @@ -273,7 +276,6 @@ gboolean dt_dev_pixelpipe_init_cached(dt_dev_pixelpipe_t *pipe, pipe->processed_height = pipe->backbuf_height = pipe->iheight = pipe->final_height = 0; pipe->nodes = NULL; pipe->backbuf_size = size; - pipe->cache_obsolete = FALSE; pipe->backbuf = NULL; pipe->backbuf_scale = 0.0f; memset(pipe->backbuf_zoom_pos, 0, sizeof(dt_dev_zoom_pos_t)); @@ -282,7 +284,6 @@ gboolean dt_dev_pixelpipe_init_cached(dt_dev_pixelpipe_t *pipe, memset(&pipe->scharr, 0, sizeof(dt_dev_detail_mask_t)); pipe->want_detail_mask = FALSE; - pipe->processing = FALSE; dt_atomic_set_int(&pipe->shutdown, DT_DEV_PIXELPIPE_STOP_NO); pipe->opencl_error = FALSE; pipe->tiling = FALSE; @@ -321,7 +322,6 @@ size_t dt_get_available_pipe_mem(const dt_dev_pixelpipe_t *pipe) static void get_output_format(dt_iop_module_t *module, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece, - dt_develop_t *dev, dt_iop_buffer_dsc_t *dsc) { if(module) return module->output_format(module, pipe, piece, dsc); @@ -348,7 +348,7 @@ void dt_dev_pixelpipe_set_input(dt_dev_pixelpipe_t *pipe, pipe->iscale = iscale; pipe->input = input; pipe->image = dev->image_storage; - get_output_format(NULL, pipe, NULL, dev, &pipe->dsc); + get_output_format(NULL, pipe, NULL, &pipe->dsc); } void dt_dev_pixelpipe_set_icc(dt_dev_pixelpipe_t *pipe, @@ -363,7 +363,7 @@ void dt_dev_pixelpipe_set_icc(dt_dev_pixelpipe_t *pipe, } void dt_dev_pixelpipe_set_shutdown(dt_dev_pixelpipe_t *pipe, - const dt_dev_pixelpipe_stopper_t stopper) + const dt_dev_pixelpipe_stopper_t stopper) { #ifndef DT_PIPE_CAS_SHUTDOWN @@ -371,16 +371,22 @@ void dt_dev_pixelpipe_set_shutdown(dt_dev_pixelpipe_t *pipe, dt_print_pipe(DT_DEBUG_PIPE | DT_DEBUG_VERBOSE, "pipe shutdown request", pipe, NULL, pipe->devid, NULL, NULL, "%s", dt_dev_pixelpipe_shutdown_to_str(stopper)); - #else +#else - int expected = DT_DEV_PIXELPIPE_STOP_NO; + int expected = DT_DEV_PIXELPIPE_PROCESSING; const gboolean new = dt_atomic_CAS_int(&pipe->shutdown, &expected, stopper); - dt_print_pipe(DT_DEBUG_PIPE | DT_DEBUG_VERBOSE, "pipe shutdown request", + if(new) + dt_print_pipe(DT_DEBUG_PIPE, "pipe shutdown request", pipe, NULL, pipe->devid, NULL, NULL, - "%s %s", - new ? "" : "failed, was", - new ? dt_dev_pixelpipe_shutdown_to_str(stopper) - : dt_dev_pixelpipe_shutdown_to_str(expected)); + "%s", dt_dev_pixelpipe_shutdown_to_str(stopper)); + else + dt_print_pipe(DT_DEBUG_PIPE | DT_DEBUG_VERBOSE, "pipe shutdown request", + pipe, NULL, pipe->devid, NULL, NULL, + "to %s %s. was %s", + dt_dev_pixelpipe_shutdown_to_str(stopper), + expected == DT_DEV_PIXELPIPE_STOP_NO ? "ignored" : "failed", + dt_dev_pixelpipe_shutdown_to_str(expected)); + #endif } @@ -466,10 +472,6 @@ void dt_dev_pixelpipe_rebuild(dt_develop_t *dev) dev->preview_pipe->changed |= DT_DEV_PIPE_REMOVE; dev->preview2.pipe->changed |= DT_DEV_PIPE_REMOVE; - dev->full.pipe->cache_obsolete = TRUE; - dev->preview_pipe->cache_obsolete = TRUE; - dev->preview2.pipe->cache_obsolete = TRUE; - // invalidate buffers and force redraw of darkroom dt_dev_invalidate_all(dev); } @@ -877,9 +879,6 @@ static void _histogram_collect_cl(const int devid, != CL_SUCCESS) { dt_free_align(tmpbuf); - dt_print_pipe(DT_DEBUG_PIPE | DT_DEBUG_OPENCL, "[histogram_collect]", - piece->pipe, piece->module, devid, roi, NULL, - "Couldn't read histogramm data"); return; } @@ -1224,10 +1223,10 @@ static void _collect_histogram_on_CPU(dt_dev_pixelpipe_t *pipe, As modules might change internal mask visualizing modes not visible via parameters we clear the blending cache line whenever we invalidate pixelpipe cache lines. */ -static inline dt_hash_t _piece_process_hash(const dt_dev_pixelpipe_iop_t *piece, - const dt_iop_roi_t *roi, - const dt_iop_module_t *module, - const int position) +static inline dt_hash_t _piece_blend_hash(const dt_dev_pixelpipe_iop_t *piece, + const dt_iop_roi_t *roi, + const dt_iop_module_t *module, + const int position) { dt_hash_t phash = dt_dev_pixelpipe_cache_hash(roi, piece->pipe, position -1); phash = dt_hash(phash, roi, sizeof(dt_iop_roi_t)); @@ -1237,14 +1236,16 @@ static inline dt_hash_t _piece_process_hash(const dt_dev_pixelpipe_iop_t *piece, return phash; } -static inline gboolean _piece_fast_blend(const dt_dev_pixelpipe_iop_t *piece, - const dt_iop_module_t *module) +static inline gboolean _piece_fast_blend(const dt_dev_pixelpipe_iop_t *piece) { + const dt_iop_module_t *module = piece->module; + return dt_pipe_is_canvas(piece->pipe) && darktable.pipe_cache && module->dev && module->dev->gui_attached && module == module->dev->gui_module + && dt_pipe_no_mask_display(piece->pipe) && dt_dev_modulegroups_test_activated(darktable.develop) && _transform_for_blend(module, piece); } @@ -1259,30 +1260,98 @@ static inline float *_get_fast_blendcache(const size_t nfloats, return pipe->bcache_data; } +#ifdef HAVE_OPENCL +static inline gboolean _writeback_clinput(dt_dev_pixelpipe_t *pipe, float *input, cl_mem img) +{ + const int width = dt_opencl_get_image_width(img); + const int height = dt_opencl_get_image_height(img); + const int el = dt_opencl_get_image_element_size(img); + + const dt_dev_pixelpipe_cache_t *cache = &pipe->cache; + size_t lsize = 0; + for(int k = DT_PIPECACHE_MIN; k < cache->entries; k++) + { + if(cache->data[k] == input) + { + lsize = cache->size[k]; + break; + } + } + if(lsize == (size_t)width * height * el) + return dt_opencl_copy_image_to_host(pipe->devid, input, img, width, height, el) == CL_SUCCESS; + else + return FALSE; +} +#endif + /* use _module_pipe_stop to test for the just processed module leaving a flag to shutdown the pipeline immediately. - This requires extra care as we want pipecache data after the module and it's input data to be invalidated. - All dt_dev_pixelpipe_stopper_t enums must be served. + All dt_dev_pixelpipe_stopper_t enums must be served correctly, this might require + - cacheline invalidation for the module just handled + - and possibly writeback of cl_mem to input cacheline. */ static inline gboolean _module_pipe_stop(dt_dev_pixelpipe_t *pipe, const dt_iop_module_t *module, - float *input) + float *input, + void *cl_in) { const dt_dev_pixelpipe_stopper_t stopper = dt_atomic_get_int(&pipe->shutdown); - if(stopper <= DT_DEV_PIXELPIPE_STOP_NO) + if(stopper <= DT_DEV_PIXELPIPE_PROCESSING) return FALSE; dt_print_pipe(DT_DEBUG_PIPE, "module pipe stop", pipe, module, pipe->devid, NULL, NULL, "%s", dt_dev_pixelpipe_shutdown_to_str(stopper)); - // These case don't require any caretaking of cachelines + // These cases don't require any caretaking of cache as callers do that. if(stopper == DT_DEV_PIXELPIPE_STOP_NODES || stopper == DT_DEV_PIXELPIPE_STOP_HQ) return TRUE; - // stopper reflects the iop_order of the stopping mode so we must invalidate. - dt_dev_pixelpipe_invalidate_cacheline(pipe, input); - dt_dev_pixelpipe_cache_invalidate_later(pipe, stopper, "module pipe stop: "); + /* If cl_in has been provided this means for **all** other stoppers + - we might have changed cl_in data (for example by colorspace conversion) + - or there is no valid data in pipecache presented as input + so we write back cl_in to host, in case there is an error we have to invalidate. + */ +#ifdef HAVE_OPENCL + if(cl_in && input) + { + const gboolean done = _writeback_clinput(pipe, input, (cl_mem)cl_in); + if(!done) + dt_dev_pixelpipe_invalidate_cacheline(pipe, input); + } +#endif + + if(stopper == DT_DEV_PIXELPIPE_STOP_ZOOM) + dt_dev_pixelpipe_cache_invalidate_iop(pipe, module->iop_order, "zoomed pipe stop: "); + else + // stopper reflects the iop_order of the stopping mode so we must invalidate output cachelines. + dt_dev_pixelpipe_cache_invalidate_iop(pipe, stopper, "module pipe stop: "); + + return TRUE; +} + +gboolean dt_dev_pixelpipe_piece_shutdown(dt_dev_pixelpipe_iop_t *piece) +{ + dt_dev_pixelpipe_t *pipe = piece->pipe; + const dt_dev_pixelpipe_stopper_t stopper = dt_atomic_get_int(&pipe->shutdown); + if(stopper <= DT_DEV_PIXELPIPE_PROCESSING) + return FALSE; + + /* We check for any UI action requesting an early pipe shutdown as + DT_DEV_PIXELPIPE_STOP_NODES or DT_DEV_PIXELPIPE_STOP_HQ, we + - stop further processing the module thus it's output will be incorrect + - change the shutdown mode to the modules's iop_order so _module_pipe_stop() + can take care of cacheline validation. + */ + if(stopper == DT_DEV_PIXELPIPE_STOP_NODES || stopper == DT_DEV_PIXELPIPE_STOP_HQ) + { + dt_print_pipe(DT_DEBUG_PIPE, "modify piece shutdown", + pipe, piece->module, pipe->devid, NULL, NULL, "%s", + dt_dev_pixelpipe_shutdown_to_str(stopper)); + + dt_atomic_set_int(&pipe->shutdown, piece->module->iop_order); + } + return TRUE; } @@ -1407,18 +1476,10 @@ static gboolean _pixelpipe_process_on_CPU(dt_dev_pixelpipe_t *pipe, roi_in->width, roi_in->height, cst_from, cst_to, &input_format->cst, work_profile); - /* We just have changed the input data if cst_from != cst_to so - the cacheline cst does not reflect the module input colorspace any more! - Note: in opencl code path this is different as the in-data colorspace conversion - is always done in cl_mem and thus does not affect pipecache. - - Possible ways to handle this: - 1. for now we just invalidate that cacheline so if the pipe is reprocessed we won't - use wrong data. - 2. we should implement code for the pipe cache that modifies the data cst. + /* We just might have in-place changed the input data (cst_from != cst_to) + please note that cacheline dt_dev_pixelpipe_cache_t dsc has been fixed + via &input_format->cst so a cache hit in a following pipe won't concert again. */ - if(cst_from != cst_to) - dt_dev_pixelpipe_invalidate_cacheline(pipe, input); if(_pipe_has_shutdown(pipe)) return TRUE; @@ -1444,13 +1505,9 @@ static gboolean _pixelpipe_process_on_CPU(dt_dev_pixelpipe_t *pipe, TRUE, dt_dev_pixelpipe_type_to_str(pipe->type)); const size_t nfloats = bpp * roi_out->width * roi_out->height / sizeof(float); - const gboolean want_bcache = _piece_fast_blend(piece, module); - const dt_hash_t phash = want_bcache - ? _piece_process_hash(piece, roi_out, module, position) - : DT_INVALID_HASH; - const gboolean bcaching = want_bcache - ? pipe->bcache_data && phash == pipe->bcache_hash && phash != DT_INVALID_HASH - : FALSE; + const gboolean fast_blend = _piece_fast_blend(piece); + const dt_hash_t phash = fast_blend ? _piece_blend_hash(piece, roi_out, module, position) : DT_INVALID_HASH; + const gboolean bcaching = pipe->bcache_data && phash == pipe->bcache_hash && phash != DT_INVALID_HASH; if(!fitting && _piece_may_tile(piece)) { @@ -1468,13 +1525,11 @@ static gboolean _pixelpipe_process_on_CPU(dt_dev_pixelpipe_t *pipe, else { module->process_tiling(module, piece, input, *output, roi_in, roi_out, in_bpp); - if(want_bcache) + if(fast_blend) { - if(dt_pipe_no_mask_display(pipe)) - { - float *cache = _get_fast_blendcache(nfloats, phash, pipe); - if(cache) dt_iop_image_copy(cache, *output, nfloats); - } + float *cache = _get_fast_blendcache(nfloats, phash, pipe); + if(cache) + dt_iop_image_copy(cache, *output, nfloats); else pipe->bcache_hash = DT_INVALID_HASH; } @@ -1506,7 +1561,7 @@ static gboolean _pixelpipe_process_on_CPU(dt_dev_pixelpipe_t *pipe, _cpu_benchmark(pipe, module, piece, input, *output, roi_out, roi_in); module->process(module, piece, input, *output, roi_in, roi_out); - if(want_bcache) + if(fast_blend) { if(dt_pipe_no_mask_display(pipe)) { @@ -1523,6 +1578,9 @@ static gboolean _pixelpipe_process_on_CPU(dt_dev_pixelpipe_t *pipe, | PIXELPIPE_FLOW_PROCESSED_WITH_TILING); } + if(_module_pipe_stop(pipe, module, NULL, NULL)) + return TRUE; + if(pfm_dump) { dt_dump_pipe_pfm(module->op, *output, roi_out->width, roi_out->height, bpp, @@ -1729,12 +1787,6 @@ static gboolean _dev_pixelpipe_process_rec(dt_dev_pixelpipe_t *pipe, GList *pieces, const int pos) { - /* As this is done recursively we check for any dt_dev_pixelpipe_stopper_t - signal that might have been set. - */ - if(_pipe_has_shutdown(pipe)) - return TRUE; - dt_iop_roi_t roi_in = *roi_out; char module_name[256] = { 0 }; @@ -1772,7 +1824,7 @@ static gboolean _dev_pixelpipe_process_rec(dt_dev_pixelpipe_t *pipe, if(module) g_strlcpy(module_name, module->op, MIN(sizeof(module_name), sizeof(module->op))); - get_output_format(module, pipe, piece, dev, *out_format); + get_output_format(module, pipe, piece, *out_format); const size_t bpp = dt_iop_buffer_dsc_to_bpp(*out_format); const size_t bufsize = (size_t)bpp * roi_out->width * roi_out->height; @@ -2191,57 +2243,47 @@ static gboolean _dev_pixelpipe_process_rec(dt_dev_pixelpipe_t *pipe, if(possible_cl) { + if(*cl_mem_output) + { + dt_opencl_release_mem_object(*cl_mem_output); + *cl_mem_output = NULL; + } + if(cl_mem_input == NULL) + cl_mem_input = dt_opencl_copy_host_to_image(pipe->devid, input, roi_in.width, roi_in.height, in_bpp); + if(cl_mem_input == NULL) + success_opencl = FALSE; + const dt_iop_colorspace_type_t cst_from = input_cst_cl; const dt_iop_colorspace_type_t cst_to = module->input_colorspace(module, pipe, piece); const dt_iop_colorspace_type_t cst_out = module->output_colorspace(module, pipe, piece); - if(cst_from != cst_to) + gboolean valid_cl_mem_input = success_opencl; + if(cst_from != cst_to && success_opencl) + { dt_print_pipe(DT_DEBUG_PIPE, "transform colorspace", pipe, module, pipe->devid, &roi_in, NULL, "%s -> %s `%s'", dt_iop_colorspace_to_name(cst_from), dt_iop_colorspace_to_name(cst_to), work_profile ? dt_colorspaces_get_name(work_profile->type, work_profile->filename) : "no work profile"); - - gboolean didconvert = FALSE; - if(cl_mem_input != NULL || fits_on_device) - { - if(cl_mem_input == NULL) - cl_mem_input = dt_opencl_copy_host_to_image(pipe->devid, input, roi_in.width, roi_in.height, in_bpp); - - if(cl_mem_input) - { - didconvert = success_opencl = dt_ioppr_transform_image_colorspace_cl(module, pipe->devid, + valid_cl_mem_input = success_opencl = dt_ioppr_transform_image_colorspace_cl(module, pipe->devid, cl_mem_input, cl_mem_input, roi_in.width, roi_in.height, input_cst_cl, cst_to, &input_cst_cl, work_profile); - } - else - { - success_opencl = FALSE; - } } - const gboolean want_bcache = _piece_fast_blend(piece, module); - const dt_hash_t phash = want_bcache - ? _piece_process_hash(piece, roi_out, module, pos) - : DT_INVALID_HASH; - const gboolean bcaching = want_bcache && pipe->bcache_data && phash == pipe->bcache_hash && phash != DT_INVALID_HASH; + const gboolean fast_blend = _piece_fast_blend(piece); + const dt_hash_t phash = fast_blend ? _piece_blend_hash(piece, roi_out, module, pos) : DT_INVALID_HASH; + const gboolean bcaching = pipe->bcache_data && phash == pipe->bcache_hash && phash != DT_INVALID_HASH; dt_print_pipe(DT_DEBUG_PIPE, bcaching ? "from blend cache" : "process", - pipe, module, pipe->devid, &roi_in, roi_out, "%s%s%s%s%s %.1fMB", + pipe, module, pipe->devid, &roi_in, roi_out, "%s%s%s%s %.1fMB", dt_iop_colorspace_to_name(cst_to), cst_to != cst_out ? " -> " : "", cst_to != cst_out ? dt_iop_colorspace_to_name(cst_out) : "", fits_on_device ? "" : ", tiled", - *cl_mem_output ? ", stale outimage" : "", 1e-6 * (tiling.factor_cl * (m_width * m_height * m_bpp) + tiling.overhead)); - if(*cl_mem_output) - { - dt_opencl_release_mem_object(*cl_mem_output); - *cl_mem_output = NULL; - } if(fits_on_device) { @@ -2292,7 +2334,7 @@ static gboolean _dev_pixelpipe_process_rec(dt_dev_pixelpipe_t *pipe, } cl_int err = CL_SUCCESS; - if(bcaching) + if(fast_blend) { *cl_mem_output = dt_opencl_copy_host_to_image(pipe->devid, pipe->bcache_data, roi_out->width, roi_out->height, out_bpp); if(*cl_mem_output == NULL) @@ -2312,7 +2354,7 @@ static gboolean _dev_pixelpipe_process_rec(dt_dev_pixelpipe_t *pipe, else err = CL_MEM_OBJECT_ALLOCATION_FAILURE; - if(want_bcache && (err == CL_SUCCESS)) + if(fast_blend && (err == CL_SUCCESS)) { // processing was good if(dt_pipe_no_mask_display(pipe)) @@ -2362,7 +2404,7 @@ static gboolean _dev_pixelpipe_process_rec(dt_dev_pixelpipe_t *pipe, pipe->dsc.cst = module->output_colorspace(module, pipe, piece); } - if(_module_pipe_stop(pipe, module, input)) + if(_module_pipe_stop(pipe, module, input, valid_cl_mem_input ? cl_mem_input : NULL)) { dt_opencl_release_mem_object(cl_mem_input); return TRUE; @@ -2447,48 +2489,30 @@ static gboolean _dev_pixelpipe_process_rec(dt_dev_pixelpipe_t *pipe, if(_pipe_has_shutdown(pipe)) { + _writeback_clinput(pipe, input, cl_mem_input); dt_opencl_release_mem_object(cl_mem_input); return TRUE; } } else if(piece->process_tiling_ready) { - /* image is too big for direct opencl processing -> try to - * process image via tiling */ - - /* we might need to copy back valid image from device to host */ + /* image is too big for direct opencl processing -> try to process image via tiling + For lowest cl_mem pressure the current OpenCL tiling code has input & output data + in host mem so we copy back valid image from device to host + */ if(cl_mem_input != NULL) { - /* copy back to CPU buffer, then clean unneeded buffer */ - if(dt_opencl_copy_image_to_host(pipe->devid, input, cl_mem_input, roi_in.width, roi_in.height, in_bpp) - != CL_SUCCESS) - { - dt_print_pipe(DT_DEBUG_OPENCL, - "process", - pipe, module, pipe->devid, &roi_in, roi_out, "%s", - "couldn't copy input data back to host memory"); - dt_opencl_release_mem_object(cl_mem_input); - pipe->opencl_error = TRUE; - return TRUE; - } - else - input_format->cst = input_cst_cl; - + if(success_opencl) + success_opencl = dt_opencl_copy_image_to_host(pipe->devid, input, cl_mem_input, roi_in.width, roi_in.height, in_bpp) == CL_SUCCESS; dt_opencl_release_mem_object(cl_mem_input); cl_mem_input = NULL; valid_input_on_gpu_only = FALSE; } - // transform to module input colorspace if not done already - if(success_opencl && !didconvert) + if(!success_opencl) // something didn't work correctly { - dt_ioppr_transform_image_colorspace(module, input, input, - roi_in.width, roi_in.height, - input_format->cst, cst_to, &input_format->cst, - work_profile); - // the cacheline cst does not reflect the module input colorspace any more! - // FIXME let's invalidate for now dt_dev_pixelpipe_invalidate_cacheline(pipe, input); + return TRUE; } // histogram collection for module @@ -2513,7 +2537,7 @@ static gboolean _dev_pixelpipe_process_rec(dt_dev_pixelpipe_t *pipe, { err = module->process_tiling_cl(module, piece, input, *output, &roi_in, roi_out, in_bpp); - if(want_bcache && (err == CL_SUCCESS)) + if(fast_blend && (err == CL_SUCCESS)) { if(dt_pipe_no_mask_display(pipe)) { @@ -2542,7 +2566,7 @@ static gboolean _dev_pixelpipe_process_rec(dt_dev_pixelpipe_t *pipe, pipe->dsc.cst = module->output_colorspace(module, pipe, piece); } - if(_module_pipe_stop(pipe, module, input)) + if(_module_pipe_stop(pipe, module, input, NULL)) return TRUE; dt_iop_colorspace_type_t blend_cst = @@ -2664,15 +2688,9 @@ static gboolean _dev_pixelpipe_process_rec(dt_dev_pixelpipe_t *pipe, != CL_SUCCESS) { important_cl_input = FALSE; - dt_print_pipe(DT_DEBUG_OPENCL, - "process", pipe, module, pipe->devid, &roi_in, roi_out, "%s", - "couldn't copy input data back to host memory for caching"); - /* that's all we do here, we later make sure to invalidate cache line */ } else { - dt_print_pipe(DT_DEBUG_OPENCL | DT_DEBUG_VERBOSE, - "copied CL input to host for pixelpipe cache", pipe, module, pipe->devid, &roi_in, NULL); /* success: cache line is valid now, no need to invalidate it later */ valid_input_on_gpu_only = FALSE; @@ -2711,9 +2729,6 @@ static gboolean _dev_pixelpipe_process_rec(dt_dev_pixelpipe_t *pipe, if(dt_opencl_copy_image_to_host(pipe->devid, input, cl_mem_input, roi_in.width, roi_in.height, in_bpp) != CL_SUCCESS) { - dt_print_pipe(DT_DEBUG_OPENCL, - "process", pipe, module, pipe->devid, &roi_in, roi_out, "%s", - "couldn't copy input data back to host memory for caching for CPU fallback"); dt_opencl_release_mem_object(cl_mem_input); pipe->opencl_error = TRUE; return TRUE; @@ -2743,9 +2758,6 @@ static gboolean _dev_pixelpipe_process_rec(dt_dev_pixelpipe_t *pipe, if(dt_opencl_copy_image_to_host(pipe->devid, input, cl_mem_input, roi_in.width, roi_in.height, in_bpp) != CL_SUCCESS) { - dt_print_pipe(DT_DEBUG_OPENCL, - "process", pipe, module, pipe->devid, &roi_in, roi_out, "%s", - "couldn't copy input data back to host memory for CPU processing"); dt_opencl_release_mem_object(cl_mem_input); pipe->opencl_error = TRUE; return TRUE; @@ -3035,12 +3047,6 @@ static gboolean _dev_pixelpipe_process_rec_and_backcopy(dt_dev_pixelpipe_t *pipe if(err != CL_SUCCESS) { - /* this indicates a opencl problem earlier in the pipeline */ - dt_print_pipe(DT_DEBUG_OPENCL, - "process", - pipe, NULL, pipe->devid, NULL, roi_out, - "late opencl error detected while copying output back to host:%s", - cl_errstr(err)); pipe->opencl_error = TRUE; ret = TRUE; } @@ -3060,7 +3066,7 @@ gboolean dt_dev_pixelpipe_process(dt_dev_pixelpipe_t *pipe, const float scale, const int devid) { - pipe->processing = TRUE; + dt_atomic_set_int(&pipe->shutdown, DT_DEV_PIXELPIPE_PROCESSING); pipe->nocache = dt_pipe_is_image(pipe); pipe->runs++; pipe->opencl_enabled = dt_opencl_running(); @@ -3096,11 +3102,6 @@ gboolean dt_dev_pixelpipe_process(dt_dev_pixelpipe_t *pipe, // re-entry point: in case of late opencl errors we start all over // again with opencl-support disabled restart: - - // check if we should obsolete caches - if(pipe->cache_obsolete) dt_dev_pixelpipe_cache_flush(pipe); - pipe->cache_obsolete = FALSE; - // mask display off as a starting point pipe->mask_display = DT_DEV_PIXELPIPE_DISPLAY_NONE; // and blendif active @@ -3207,10 +3208,7 @@ gboolean dt_dev_pixelpipe_process(dt_dev_pixelpipe_t *pipe, // ... and in case of other errors ... if(err) - { - pipe->processing = FALSE; return TRUE; - } // terminate dt_pthread_mutex_lock(&pipe->backbuf_mutex); @@ -3248,7 +3246,6 @@ gboolean dt_dev_pixelpipe_process(dt_dev_pixelpipe_t *pipe, pipe->image.filename, pipe->image.id); dt_print_mem_usage("after pixelpipe process"); - pipe->processing = FALSE; return FALSE; } diff --git a/src/develop/pixelpipe_hb.h b/src/develop/pixelpipe_hb.h index 0c0acea08819..6491b342ce2c 100644 --- a/src/develop/pixelpipe_hb.h +++ b/src/develop/pixelpipe_hb.h @@ -31,7 +31,7 @@ G_BEGIN_DECLS #define DT_PIPECACHE_MIN 2 -// #define DT_PIPE_CAS_SHUTDOWN +#define DT_PIPE_CAS_SHUTDOWN /** cached distorted mask at a geometric module's output boundary. * used to avoid re-distorting masks from scratch when multiple @@ -107,11 +107,12 @@ typedef enum dt_dev_pixelpipe_status_t This requires special care in - _dev_pixelpipe_process_rec() - dt_dev_process_image_job() - possibly invalidating wrong module input/output data in the pixelpipe cache, - ensure either an immediate restart of the pipe or exit of dt_dev_process_image_job() + possibly invalidating wrong output data in the pixelpipe cache and ensuring + either an immediate restart of the pipe or exit of dt_dev_process_image_job() with an error flag. - A reminder, when setting pipe->shutdown we might have to do that via dt_atomic_CAS_int() - with wxpected DT_DEV_PIXELPIPE_STOP_NO to avoid overwriting an earlier shutdown writing. + + When setting pipe->shutdown from outside of pixelpipe internals we should use + dt_dev_pixelpipe_set_shutdown() for logs and making sure we don't overwrite shutdown. A summary about how these shutdown modes are supposed to work. @@ -126,19 +127,24 @@ typedef enum dt_dev_pixelpipe_status_t Used to switch between darkroom HQ modes. Requires a restart of the pipe but pixelpipe cache can stay. + DT_DEV_PIXELPIPE_STOP_ZOOM + As we stopped inside a module the output cacheline must be invalidated. + DT_DEV_PIXELPIPE_STOP_LAST If the shutdown value is >= DT_DEV_PIXELPIPE_STOP_LAST it is understood as the iop_order - of a module. - Any module might set pipe->shutdown to it's iop_order, this is checked while processing - the pipe and if detected the piece input data and all pipe cachelines with at least - this iop_order will be invalidated. + of a module. Any module can use dt_dev_pixelpipe_set_shutdown() to it's iop_order at runtime, + this is checked in _module_pipe_stop() while processing the pipe. + + _module_pipe_stop() ensures valid input cache data and possibly invalidates output cachelines. */ typedef enum dt_dev_pixelpipe_stopper_t { DT_DEV_PIXELPIPE_STOP_NO = 0, + DT_DEV_PIXELPIPE_PROCESSING, DT_DEV_PIXELPIPE_STOP_NODES, DT_DEV_PIXELPIPE_STOP_HQ, + DT_DEV_PIXELPIPE_STOP_ZOOM, DT_DEV_PIXELPIPE_STOP_LAST, } dt_dev_pixelpipe_stopper_t; @@ -159,8 +165,6 @@ typedef struct dt_dev_pixelpipe_t { // store history/zoom caches dt_dev_pixelpipe_cache_t cache; - // set to TRUE in order to obsolete old cache entries on next pixelpipe run - gboolean cache_obsolete; uint64_t runs; // used only for pixelpipe cache statistics // input buffer float *input; @@ -211,16 +215,7 @@ typedef struct dt_dev_pixelpipe_t gboolean nocache; dt_imgid_t output_imgid; - // working? - gboolean processing; - /* shutting down? - can be used in various ways defined in dt_dev_pixelpipe_stopper_t, in all cases the - running pipe is stopped asap - If we don't use one of the enum values this is interpreted as the iop_order of the module - that has set this in case of an error condition or other reasons that request a re-run of the pipe. - In those cases we assume cachelines after this module and the input of the stopper module - are not valid cachelines any more so the pixelpipe takes care of this. - */ + // pipe running & shutdown modes defined as dt_dev_pixelpipe_stopper_t dt_atomic_int shutdown; // opencl enabled for this pixelpipe? gboolean opencl_enabled; @@ -317,6 +312,14 @@ static inline gboolean dt_pipe_mask_display(const dt_dev_pixelpipe_t *pipe) { return pipe->mask_display != DT_DEV_PIXELPIPE_DISPLAY_NONE; } +static inline gboolean dt_pipe_processing(dt_dev_pixelpipe_t *pipe) +{ + return dt_atomic_get_int(&pipe->shutdown) == DT_DEV_PIXELPIPE_PROCESSING; +} +static inline gboolean dt_pipe_started(dt_dev_pixelpipe_t *pipe) +{ + return dt_atomic_get_int(&pipe->shutdown) >= DT_DEV_PIXELPIPE_PROCESSING; +} // report pipe->type as textual string const char *dt_dev_pixelpipe_type_to_str(const dt_dev_pixelpipe_type_t pipe_type); @@ -324,7 +327,6 @@ const char *dt_dev_pixelpipe_type_to_str(const dt_dev_pixelpipe_type_t pipe_type const char *dt_dev_pixelpipe_shutdown_to_str(const dt_dev_pixelpipe_stopper_t stopper); // sets pipe->shutdown in atomic mode -// If DT_PIPE_CAS_SHUTDOWN is defined do that only if shutdown was DT_DEV_PIXELPIPE_STOP_NO void dt_dev_pixelpipe_set_shutdown(dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_stopper_t stopper); // inits the pixelpipe with plain passthrough input/output and empty input and default caching settings. @@ -422,6 +424,9 @@ void dt_dev_pixelpipe_disable_after(dt_dev_pixelpipe_t *pipe, const char *op); // disable given op and all that comes before it in the pipe: void dt_dev_pixelpipe_disable_before(dt_dev_pixelpipe_t *pipe, const char *op); +// pieces can test for a pipe shutdown request within module->process(). +gboolean dt_dev_pixelpipe_piece_shutdown(dt_dev_pixelpipe_iop_t *piece); + // helper function to pass a raster mask through a (so far) processed pipe float *dt_dev_get_raster_mask(dt_dev_pixelpipe_iop_t *piece, const struct dt_iop_module_t *raster_mask_source, diff --git a/src/gui/color_picker_proxy.c b/src/gui/color_picker_proxy.c index 7b2a59e37a79..917cc0d2641f 100644 --- a/src/gui/color_picker_proxy.c +++ b/src/gui/color_picker_proxy.c @@ -300,7 +300,6 @@ static void _iop_color_picker_pickerdata_ready_callback(gpointer instance, // will set the work_profile if needed. // FIXME: is this overdoing it? see #14812 pipe->changed |= DT_DEV_PIPE_REMOVE; - pipe->cache_obsolete = TRUE; // iops only need new picker data if the pointer has moved if(_record_point_area(picker)) diff --git a/src/iop/diffuse.c b/src/iop/diffuse.c index ab2f94010b5e..1a04c6c0c8d7 100644 --- a/src/iop/diffuse.c +++ b/src/iop/diffuse.c @@ -1414,7 +1414,7 @@ void process(dt_iop_module_t *self, in = temp1; } - for(int it = 0; it < iterations; it++) + for(int it = 0; it < iterations && !dt_dev_pixelpipe_piece_shutdown(piece); it++) { if(it == 0) { @@ -1680,7 +1680,7 @@ int process_cl(dt_iop_module_t *self, in = temp1; } - for(int it = 0; it < iterations && err == CL_SUCCESS; it++) + for(int it = 0; it < iterations && err == CL_SUCCESS && !dt_dev_pixelpipe_piece_shutdown(piece); it++) { if(it == 0) { diff --git a/src/iop/toneequal.c b/src/iop/toneequal.c index 7b6440c4d88e..3513d1ebbb06 100644 --- a/src/iop/toneequal.c +++ b/src/iop/toneequal.c @@ -1802,7 +1802,7 @@ static void auto_adjust_exposure_boost(GtkWidget *quad, dt_iop_module_t *self) return; } - if(!g->luminance_valid || self->dev->full.pipe->processing || !g->histogram_valid) + if(!g->luminance_valid || dt_pipe_processing(self->dev->full.pipe) || !g->histogram_valid) { dt_control_log(_("wait for the preview to finish recomputing")); return; @@ -1868,7 +1868,7 @@ static void auto_adjust_contrast_boost(GtkWidget *quad, dt_iop_module_t *self) return; } - if(!g->luminance_valid || self->dev->full.pipe->processing || !g->histogram_valid) + if(!g->luminance_valid || dt_pipe_processing(self->dev->full.pipe) || !g->histogram_valid) { dt_control_log(_("wait for the preview to finish recomputing")); return; @@ -1993,7 +1993,7 @@ static void switch_cursors(dt_iop_module_t *self) // do nothing and let the app decide return; } - else if((self->dev->full.pipe->processing + else if((dt_pipe_processing(self->dev->full.pipe) || self->dev->full.pipe->status == DT_DEV_PIXELPIPE_DIRTY || self->dev->preview_pipe->status == DT_DEV_PIXELPIPE_DIRTY) && g->cursor_valid) @@ -2006,7 +2006,7 @@ static void switch_cursors(dt_iop_module_t *self) dt_control_queue_redraw_center(); } - else if(g->cursor_valid && !self->dev->full.pipe->processing) + else if(g->cursor_valid && !dt_pipe_processing(self->dev->full.pipe)) { // if pipe is clean and idle and cursor is on preview, // hide GTK cursor because we display our custom one @@ -2078,7 +2078,7 @@ int mouse_moved(dt_iop_module_t *self, dt_iop_gui_leave_critical_section(self); // store the actual exposure too, to spare I/O op - if(g->cursor_valid && !dev->full.pipe->processing && g->luminance_valid) + if(g->cursor_valid && !dt_pipe_processing(dev->full.pipe) && g->luminance_valid) g->cursor_exposure = log2f(_luminance_from_module_buffer(self)); switch_cursors(self); @@ -2196,7 +2196,7 @@ int scrolled(dt_iop_module_t *self, || !g->luminance_valid || !g->interpolation_valid || !g->user_param_valid - || dev->full.pipe->processing + || dt_pipe_processing(dev->full.pipe) || !g->has_focus; dt_iop_gui_leave_critical_section(self); @@ -2357,7 +2357,7 @@ void gui_post_expose(dt_iop_module_t *self, const gboolean fail = !g->cursor_valid || !g->interpolation_valid - || dev->full.pipe->processing + || dt_pipe_processing(dev->full.pipe) || !g->has_focus; dt_iop_gui_leave_critical_section(self); diff --git a/src/views/darkroom.c b/src/views/darkroom.c index dae3d0311977..a60e9dece9a4 100644 --- a/src/views/darkroom.c +++ b/src/views/darkroom.c @@ -1758,13 +1758,10 @@ static void _latescaling_quickbutton_clicked(GtkWidget *w, dt_conf_set_bool("darkroom/ui/late_scaling/enabled", dev->late_scaling.enabled); // we just toggled off and had one of HQ pipelines running - if(!dev->late_scaling.enabled - && (dev->full.pipe->processing - || (dev->second_wnd && dev->preview2.pipe->processing))) + if(!dev->late_scaling.enabled) { - if(dev->full.pipe->processing) - dt_dev_pixelpipe_set_shutdown(dev->full.pipe, DT_DEV_PIXELPIPE_STOP_HQ); - if(dev->second_wnd && dev->preview2.pipe->processing) + dt_dev_pixelpipe_set_shutdown(dev->full.pipe, DT_DEV_PIXELPIPE_STOP_HQ); + if(dev->second_wnd) dt_dev_pixelpipe_set_shutdown(dev->preview2.pipe, DT_DEV_PIXELPIPE_STOP_HQ); // do it the hard way for safety @@ -3450,14 +3447,6 @@ void enter(dt_view_t *self) dt_print(DT_DEBUG_CONTROL, "[run_job+] 11 %f in darkroom mode", dt_get_wtime()); dt_develop_t *dev = self->data; - // Reset shutdown flags on all pipes - they may still be set from previous session - if(dev->full.pipe) - dt_atomic_set_int(&dev->full.pipe->shutdown, DT_DEV_PIXELPIPE_STOP_NO); - if(dev->preview_pipe) - dt_atomic_set_int(&dev->preview_pipe->shutdown, DT_DEV_PIXELPIPE_STOP_NO); - if(dev->preview2.pipe) - dt_atomic_set_int(&dev->preview2.pipe->shutdown, DT_DEV_PIXELPIPE_STOP_NO); - if(!dev->form_gui) { dev->form_gui = (dt_masks_form_gui_t *)calloc(1, sizeof(dt_masks_form_gui_t)); @@ -4672,7 +4661,6 @@ static gboolean _second_window_configure_callback(GtkWidget *da, // pipe needs to be reconstructed dev->preview2.pipe->status = DT_DEV_PIXELPIPE_DIRTY; dev->preview2.pipe->changed |= DT_DEV_PIPE_REMOVE; - dev->preview2.pipe->cache_obsolete = TRUE; // If we have a pinned image, update its viewport dimensions too dt_develop_t *pinned_dev = dev->preview2_pinned ? dev->preview2_pinned_dev : NULL; @@ -4685,7 +4673,6 @@ static gboolean _second_window_configure_callback(GtkWidget *da, pinned_port->orig_height = event->height; pinned_port->pipe->status = DT_DEV_PIXELPIPE_DIRTY; pinned_port->pipe->changed |= DT_DEV_PIPE_REMOVE; - pinned_port->pipe->cache_obsolete = TRUE; } } @@ -4850,7 +4837,7 @@ static void _darkroom_ui_second_window_cleanup(dt_develop_t *dev) // Signal main preview2 pipe to stop and wait for any pending jobs if(dev->preview2.pipe) { - dt_atomic_set_int(&dev->preview2.pipe->shutdown, DT_DEV_PIXELPIPE_STOP_NODES); + dt_dev_pixelpipe_set_shutdown(dev->preview2.pipe, DT_DEV_PIXELPIPE_STOP_NODES); dt_pthread_mutex_lock(&dev->preview2.pipe->mutex); dt_pthread_mutex_unlock(&dev->preview2.pipe->mutex); dt_pthread_mutex_lock(&dev->preview2.pipe->busy_mutex);