Files
armorpaint/paint/sources/util/util_layer.c
T
2026-06-11 20:01:25 +02:00

1284 lines
43 KiB
C

#include "../global.h"
#if defined(IRON_ANDROID) || defined(IRON_IOS)
i32 layers_max_layers = 18;
#else
i32 layers_max_layers = 255;
#endif
uv_type_t _layers_uv_type;
mat4_t _layers_decal_mat;
i32 _layers_position;
i32 _layers_base_color;
f32 _layers_occlusion;
f32 _layers_roughness;
f32 _layers_metallic;
void util_layer_update_preview() {
if (g_context->layers_preview_dirty) {
g_context->layers_preview_dirty = false;
g_context->layer_preview_dirty = false;
g_context->mask_preview_last = NULL;
// Update all layer previews
for (i32 i = 0; i < g_project->_->layers->length; ++i) {
slot_layer_t *l = g_project->_->layers->buffer[i];
if (slot_layer_is_group(l)) {
continue;
}
gpu_texture_t *target = l->texpaint_preview;
if (target == NULL) {
continue;
}
gpu_texture_t *source = l->texpaint;
draw_begin(target, true, 0x00000000);
// draw_set_pipeline(l.is_mask() ? pipes_copy8 : pipes_copy);
draw_set_pipeline(pipes_copy); // texpaint_preview is always RGBA32 for now
draw_scaled_image(source, 0, 0, target->width, target->height);
draw_set_pipeline(NULL);
draw_end();
}
ui_base_hwnds->buffer[TAB_AREA_SIDEBAR0]->redraws = 2;
}
if (g_context->layer != NULL && g_context->layer_preview_dirty && !slot_layer_is_group(g_context->layer)) {
g_context->layer_preview_dirty = false;
g_context->mask_preview_last = NULL;
// Update layer preview
slot_layer_t *l = g_context->layer;
gpu_texture_t *target = l->texpaint_preview;
if (target != NULL) {
gpu_texture_t *source = l->texpaint;
draw_begin(target, true, 0x00000000);
// draw_set_pipeline(raw.layer.is_mask() ? pipes_copy8 : pipes_copy);
draw_set_pipeline(pipes_copy); // texpaint_preview is always RGBA32 for now
draw_scaled_image(source, 0, 0, target->width, target->height);
draw_set_pipeline(NULL);
draw_end();
ui_base_hwnds->buffer[TAB_AREA_SIDEBAR0]->redraws = 2;
}
}
}
void layers_init() {
slot_layer_clear(g_project->_->layers->buffer[0], color_from_floats(layers_default_base, layers_default_base, layers_default_base, 1.0), NULL, 1.0,
layers_default_rough, 0.0);
}
void layers_resize() {
if (config_get_texture_res_x() >= 16384 || config_get_texture_res_y() >= 16384) { // Save memory for >=16k
g_config->undo_steps = 1;
while (history_undo_layers->length > g_config->undo_steps) {
slot_layer_t *l = array_pop(history_undo_layers);
sys_notify_on_next_frame(&slot_layer_unload, l);
}
}
for (i32 i = 0; i < g_project->_->layers->length; ++i) {
slot_layer_t *l = g_project->_->layers->buffer[i];
slot_layer_resize_and_set_bits(l);
}
for (i32 i = 0; i < history_undo_layers->length; ++i) {
slot_layer_t *l = history_undo_layers->buffer[i];
slot_layer_resize_and_set_bits(l);
}
any_map_t *rts = render_path_render_targets;
render_target_t *blend0 = any_map_get(rts, "texpaint_blend0");
gpu_texture_t *_texpaint_blend0 = blend0->_image;
gpu_delete_texture(_texpaint_blend0);
blend0->width = config_get_texture_res_x();
blend0->height = config_get_texture_res_y();
blend0->_image = gpu_create_render_target(config_get_texture_res_x(), config_get_texture_res_y(), GPU_TEXTURE_FORMAT_R8);
render_target_t *blend1 = any_map_get(rts, "texpaint_blend1");
gpu_texture_t *_texpaint_blend1 = blend1->_image;
gpu_delete_texture(_texpaint_blend1);
blend1->width = config_get_texture_res_x();
blend1->height = config_get_texture_res_y();
blend1->_image = gpu_create_render_target(config_get_texture_res_x(), config_get_texture_res_y(), GPU_TEXTURE_FORMAT_R8);
g_context->brush_blend_dirty = true;
render_target_t *blur = any_map_get(rts, "texpaint_blur");
if (blur != NULL) {
gpu_texture_t *_texpaint_blur = blur->_image;
gpu_delete_texture(_texpaint_blur);
f32 size_x = math_floor(config_get_texture_res_x() * 0.95);
f32 size_y = math_floor(config_get_texture_res_y() * 0.95);
blur->width = size_x;
blur->height = size_y;
blur->_image = gpu_create_render_target(size_x, size_y, GPU_TEXTURE_FORMAT_RGBA32);
}
if (render_path_paint_live_layer != NULL) {
slot_layer_resize_and_set_bits(render_path_paint_live_layer);
}
render_target_t *sculpt_ref = any_map_get(rts, "texpaint_sculpt_ref");
if (sculpt_ref != NULL) {
gpu_delete_texture(sculpt_ref->_image);
sculpt_ref->width = config_get_texture_res_x();
sculpt_ref->height = config_get_texture_res_y();
sculpt_ref->_image = gpu_create_render_target(config_get_texture_res_x(), config_get_texture_res_y(), GPU_TEXTURE_FORMAT_RGBA128);
}
render_target_t *sculpt_base = any_map_get(rts, "texpaint_sculpt_base");
if (sculpt_base != NULL) {
gpu_delete_texture(sculpt_base->_image);
sculpt_base->width = config_get_texture_res_x();
sculpt_base->height = config_get_texture_res_y();
sculpt_base->_image = gpu_create_render_target(config_get_texture_res_x(), config_get_texture_res_y(), GPU_TEXTURE_FORMAT_RGBA128);
}
render_path_raytrace_ready = false; // Rebuild baketex
g_context->ddirty = 2;
}
void layers_set_bits() {
for (i32 i = 0; i < g_project->_->layers->length; ++i) {
slot_layer_t *l = g_project->_->layers->buffer[i];
slot_layer_resize_and_set_bits(l);
}
for (i32 i = 0; i < history_undo_layers->length; ++i) {
slot_layer_t *l = history_undo_layers->buffer[i];
slot_layer_resize_and_set_bits(l);
}
}
void layers_make_temp_img() {
slot_layer_t *l = g_project->_->layers->buffer[0];
if (layers_temp_image != NULL && (layers_temp_image->width != l->texpaint->width || layers_temp_image->height != l->texpaint->height ||
layers_temp_image->format != l->texpaint->format)) {
render_target_t *_temptex0 = any_map_get(render_path_render_targets, "temptex0");
gpu_delete_texture(_temptex0->_image);
map_delete(render_path_render_targets, "temptex0");
gc_unroot(layers_temp_image);
layers_temp_image = NULL;
}
if (layers_temp_image == NULL) {
char *format = base_bits_handle->i == TEXTURE_BITS_BITS8 ? "RGBA32" : base_bits_handle->i == TEXTURE_BITS_BITS16 ? "RGBA64" : "RGBA128";
render_target_t *t = render_target_create();
t->name = "temptex0";
t->width = l->texpaint->width;
t->height = l->texpaint->height;
t->format = string_copy(format);
render_target_t *rt = render_path_create_render_target(t);
gc_unroot(layers_temp_image);
layers_temp_image = rt->_image;
gc_root(layers_temp_image);
}
}
void layers_make_temp_mask_img() {
if (pipes_temp_mask_image != NULL &&
(pipes_temp_mask_image->width != config_get_texture_res_x() || pipes_temp_mask_image->height != config_get_texture_res_y())) {
gpu_texture_t *_temp_mask_image = pipes_temp_mask_image;
gpu_delete_texture(_temp_mask_image);
gc_unroot(pipes_temp_mask_image);
pipes_temp_mask_image = NULL;
}
if (pipes_temp_mask_image == NULL) {
gc_unroot(pipes_temp_mask_image);
// pipes_temp_mask_image = gpu_create_render_target(config_get_texture_res_x(), config_get_texture_res_y(), GPU_TEXTURE_FORMAT_R8);
pipes_temp_mask_image = gpu_create_render_target(config_get_texture_res_x(), config_get_texture_res_y(), GPU_TEXTURE_FORMAT_RGBA32);
gc_root(pipes_temp_mask_image);
}
}
void layers_make_export_img() {
slot_layer_t *l = g_project->_->layers->buffer[0];
if (layers_expa != NULL &&
(layers_expa->width != l->texpaint->width || layers_expa->height != l->texpaint->height || layers_expa->format != l->texpaint->format)) {
gpu_texture_t *_expa = layers_expa;
gpu_texture_t *_expb = layers_expb;
gpu_texture_t *_expc = layers_expc;
gpu_delete_texture(_expa);
gpu_delete_texture(_expb);
gpu_delete_texture(_expc);
gc_unroot(layers_expa);
layers_expa = NULL;
gc_unroot(layers_expb);
layers_expb = NULL;
gc_unroot(layers_expc);
layers_expc = NULL;
map_delete(render_path_render_targets, "expa");
map_delete(render_path_render_targets, "expb");
map_delete(render_path_render_targets, "expc");
}
if (layers_expa == NULL) {
char *format = base_bits_handle->i == TEXTURE_BITS_BITS8 ? "RGBA32" : base_bits_handle->i == TEXTURE_BITS_BITS16 ? "RGBA64" : "RGBA128";
{
render_target_t *t = render_target_create();
t->name = "expa";
t->width = l->texpaint->width;
t->height = l->texpaint->height;
t->format = string_copy(format);
render_target_t *rt = render_path_create_render_target(t);
gc_unroot(layers_expa);
layers_expa = rt->_image;
gc_root(layers_expa);
}
{
render_target_t *t = render_target_create();
t->name = "expb";
t->width = l->texpaint->width;
t->height = l->texpaint->height;
t->format = string_copy(format);
render_target_t *rt = render_path_create_render_target(t);
gc_unroot(layers_expb);
layers_expb = rt->_image;
gc_root(layers_expb);
}
{
render_target_t *t = render_target_create();
t->name = "expc";
t->width = l->texpaint->width;
t->height = l->texpaint->height;
t->format = string_copy(format);
render_target_t *rt = render_path_create_render_target(t);
gc_unroot(layers_expc);
layers_expc = rt->_image;
gc_root(layers_expc);
}
}
}
void layers_commands_merge_pack(gpu_pipeline_t *pipe, gpu_texture_t *i0, gpu_texture_t *i1, gpu_texture_t *i1pack, f32 i1mask_opacity, gpu_texture_t *i1texmask,
i32 i1blending) {
_gpu_begin(i0, NULL, NULL, GPU_CLEAR_NONE, 0, 0.0);
gpu_set_pipeline(pipe);
gpu_set_texture(pipes_tex0, i1);
gpu_set_texture(pipes_tex1, i1pack);
gpu_set_texture(pipes_texmask, i1texmask);
gpu_set_texture(pipes_texa, layers_temp_image);
gpu_set_float(pipes_opac, i1mask_opacity);
gpu_set_float(pipes_tex1w, i1pack->width);
gpu_set_int(pipes_blending, i1blending);
gpu_set_vertex_buffer(const_data_screen_aligned_vb);
gpu_set_index_buffer(const_data_screen_aligned_ib);
gpu_draw();
gpu_end();
}
bool layers_is_fill_material() {
if (g_context->tool == TOOL_TYPE_MATERIAL) {
return true;
}
slot_material_t *m = g_context->material;
for (i32 i = 0; i < g_project->_->layers->length; ++i) {
slot_layer_t *l = g_project->_->layers->buffer[i];
if (l->fill_material == m) {
return true;
}
}
return false;
}
void layers_update_fill_layers() {
if (bake_texture_node_baking)
return;
slot_layer_t *_layer = g_context->layer;
tool_type_t _tool = g_context->tool;
i32 _fill_type = g_context->fill_type;
gpu_texture_t *current = NULL;
if (g_context->tool == TOOL_TYPE_MATERIAL) {
if (render_path_paint_live_layer == NULL) {
gc_unroot(render_path_paint_live_layer);
render_path_paint_live_layer = slot_layer_create("_live", LAYER_SLOT_TYPE_LAYER, NULL);
gc_root(render_path_paint_live_layer);
}
current = _draw_current;
bool in_use = gpu_in_use;
if (in_use)
draw_end();
g_context->tool = TOOL_TYPE_FILL;
g_context->fill_type = FILL_TYPE_OBJECT;
render_path_paint_set_plane_mesh();
make_material_parse_paint_material(false);
g_context->pdirty = 1;
render_path_paint_use_live_layer(true);
render_path_paint_commands_paint(false);
render_path_paint_dilate(true, true);
render_path_paint_use_live_layer(false);
g_context->tool = _tool;
g_context->fill_type = _fill_type;
g_context->pdirty = 0;
g_context->rdirty = 2;
render_path_paint_restore_plane_mesh();
make_material_parse_paint_material(true);
ui_view2d_hwnd->redraws = 2;
if (in_use)
draw_begin(current, false, 0);
return;
}
bool has_fill_layer = false;
bool has_fill_mask = false;
for (i32 i = 0; i < g_project->_->layers->length; ++i) {
slot_layer_t *l = g_project->_->layers->buffer[i];
if (slot_layer_is_layer(l) && l->fill_material == g_context->material) {
has_fill_layer = true;
}
}
for (i32 i = 0; i < g_project->_->layers->length; ++i) {
slot_layer_t *l = g_project->_->layers->buffer[i];
if (slot_layer_is_mask(l) && l->fill_material == g_context->material) {
has_fill_mask = true;
}
}
if (has_fill_layer || has_fill_mask) {
current = _draw_current;
bool in_use = gpu_in_use;
if (in_use)
draw_end();
g_context->pdirty = 1;
g_context->tool = TOOL_TYPE_FILL;
g_context->fill_type = FILL_TYPE_OBJECT;
if (has_fill_layer) {
bool first = true;
for (i32 i = 0; i < g_project->_->layers->length; ++i) {
slot_layer_t *l = g_project->_->layers->buffer[i];
if (slot_layer_is_layer(l) && l->fill_material == g_context->material) {
g_context->layer = l;
if (first) {
first = false;
make_material_parse_paint_material(false);
}
layers_set_object_mask();
if (l->texpaint_sculpt != NULL) {
i32 tid = l->id;
i32 hid = history_undo_i - 1 < 0 ? g_config->undo_steps - 1 : history_undo_i - 1;
sculpt_import_mesh_pack_to_texture(l->texpaint_sculpt);
render_path_set_target(string("texpaint_sculpt_undo%d", hid), NULL, NULL, GPU_CLEAR_NONE, 0, 0.0);
render_path_bind_target(string("texpaint_sculpt%d", tid), "tex");
render_path_draw_shader("Scene/copy_pass/copyRGBA128_pass");
render_path_sculpt_commands();
}
else {
slot_layer_clear(l, 0x00000000, NULL, 1.0, layers_default_rough, 0.0);
render_path_paint_commands_paint(false);
render_path_paint_dilate(true, true);
}
}
}
}
if (has_fill_mask) {
bool first = true;
for (i32 i = 0; i < g_project->_->layers->length; ++i) {
slot_layer_t *l = g_project->_->layers->buffer[i];
if (slot_layer_is_mask(l) && l->fill_material == g_context->material) {
g_context->layer = l;
if (first) {
first = false;
make_material_parse_paint_material(false);
}
layers_set_object_mask();
slot_layer_clear(l, 0x00000000, NULL, 1.0, layers_default_rough, 0.0);
render_path_paint_commands_paint(false);
render_path_paint_dilate(true, true);
}
}
}
g_context->pdirty = 0;
g_context->ddirty = 2;
g_context->rdirty = 2;
g_context->layers_preview_dirty = true; // Repaint all layer previews as multiple layers might have changed.
if (in_use)
draw_begin(current, false, 0);
g_context->layer = _layer;
layers_set_object_mask();
g_context->tool = _tool;
g_context->fill_type = _fill_type;
make_material_parse_paint_material(false);
}
}
bool layers_is_path_material() {
slot_material_t *m = g_context->material;
for (i32 i = 0; i < g_project->_->layers->length; ++i) {
slot_layer_t *l = g_project->_->layers->buffer[i];
if (slot_layer_is_path(l) && l->path_material == m) {
return true;
}
}
return false;
}
void layers_update_path_layers() {
for (i32 i = 0; i < g_project->_->layers->length; ++i) {
slot_layer_t *l = g_project->_->layers->buffer[i];
if (slot_layer_is_path(l) && l->path_material == g_context->material) {
util_layer_repaint_path(l);
}
}
}
void layers_update_fill_layer(bool parse_paint) {
gpu_texture_t *current = _draw_current;
bool in_use = gpu_in_use;
if (in_use)
draw_end();
tool_type_t _tool = g_context->tool;
i32 _fill_type = g_context->fill_type;
g_context->tool = TOOL_TYPE_FILL;
g_context->fill_type = FILL_TYPE_OBJECT;
g_context->pdirty = 1;
if (g_context->layer->texpaint_sculpt != NULL) {
i32 tid = g_context->layer->id;
i32 hid = history_undo_i - 1 < 0 ? g_config->undo_steps - 1 : history_undo_i - 1;
sculpt_import_mesh_pack_to_texture(g_context->layer->texpaint_sculpt);
render_path_set_target(string("texpaint_sculpt_undo%d", hid), NULL, NULL, GPU_CLEAR_NONE, 0, 0.0);
render_path_bind_target(string("texpaint_sculpt%d", tid), "tex");
render_path_draw_shader("Scene/copy_pass/copyRGBA128_pass");
if (parse_paint) {
make_material_parse_paint_material(false);
}
render_path_sculpt_commands();
}
else {
slot_layer_clear(g_context->layer, 0x00000000, NULL, 1.0, layers_default_rough, 0.0);
if (parse_paint) {
make_material_parse_paint_material(false);
}
render_path_paint_commands_paint(false);
render_path_paint_dilate(true, true);
}
g_context->rdirty = 2;
g_context->tool = _tool;
g_context->fill_type = _fill_type;
if (in_use)
draw_begin(current, false, 0);
}
void layers_update_linked_layers() {
slot_material_t *_material = g_context->material;
bool any_linked = false;
for (i32 i = 0; i < g_project->_->materials->length; ++i) {
slot_material_t *m = g_project->_->materials->buffer[i];
bool has_linked = false;
for (i32 j = 0; j < m->canvas->nodes->length; ++j) {
ui_node_t *node = m->canvas->nodes->buffer[j];
if (string_equals(node->type, "LAYER") || string_equals(node->type, "LAYER_MASK")) {
has_linked = true;
break;
}
}
if (!has_linked) {
continue;
}
any_linked = true;
g_context->material = m;
layers_update_fill_layers();
}
g_context->material = _material;
if (any_linked) {
make_material_parse_paint_material(false);
}
}
void layers_set_object_mask() {
string_array_t *ar = any_array_create_from_raw(
(void *[]){
tr("None"),
},
1);
for (i32 i = 0; i < g_project->_->paint_objects->length; ++i) {
mesh_object_t *p = g_project->_->paint_objects->buffer[i];
any_array_push(ar, p->base->name);
}
i32 mask = context_object_mask_used() ? slot_layer_get_object_mask(g_context->layer) : 0;
if (context_layer_filter_used()) {
mask = g_context->layer_filter;
}
if (mask > 0) {
if (g_context->merged_object != NULL) {
g_context->merged_object->base->visible = false;
}
mesh_object_t *o = g_project->_->paint_objects->buffer[0];
for (i32 i = 0; i < g_project->_->paint_objects->length; ++i) {
mesh_object_t *p = g_project->_->paint_objects->buffer[i];
char *mask_name = ar->buffer[mask];
if (string_equals(p->base->name, mask_name)) {
o = p;
break;
}
}
context_select_paint_object(o);
}
else {
i32 atlas_mask = g_context->layer_filter > g_project->_->paint_objects->length ? g_context->layer_filter : slot_layer_get_object_mask(g_context->layer);
bool is_atlas = atlas_mask > g_project->_->paint_objects->length;
if (g_context->merged_object == NULL || is_atlas || g_context->merged_object_is_atlas) {
mesh_object_t_array_t *visibles = is_atlas ? project_get_atlas_objects(atlas_mask) : NULL;
util_mesh_merge(visibles);
}
context_select_paint_object(context_main_object());
g_context->paint_object->skip_context = "paint";
g_context->merged_object->base->visible = true;
}
util_uv_dilatemap_cached = false;
}
void tab_layers_apply_filter(i32 filter) {
g_context->layer_filter = filter;
char *filter_name = NULL;
if (filter > 0 && filter <= g_project->_->paint_objects->length) {
filter_name = g_project->_->paint_objects->buffer[filter - 1]->base->name;
}
else if (filter > g_project->_->paint_objects->length) {
string_array_t *atlases = project_get_used_atlases();
if (atlases != NULL) {
filter_name = atlases->buffer[filter - g_project->_->paint_objects->length - 1];
}
}
for (i32 i = 0; i < g_project->_->paint_objects->length; ++i) {
mesh_object_t *p = g_project->_->paint_objects->buffer[i];
p->base->visible = filter == 0 || (filter_name != NULL && string_equals(p->base->name, filter_name)) || project_is_atlas_object(p);
}
if (filter == 0 && g_context->merged_object_is_atlas) {
util_mesh_merge(NULL);
}
else if (filter > g_project->_->paint_objects->length) {
mesh_object_t_array_t *visibles = any_array_create_from_raw((void *[]){}, 0);
for (i32 i = 0; i < g_project->_->paint_objects->length; ++i) {
mesh_object_t *p = g_project->_->paint_objects->buffer[i];
if (p->base->visible) {
any_array_push(visibles, p);
}
}
util_mesh_merge(visibles);
}
layers_set_object_mask();
util_uv_uvmap_cached = false;
g_context->ddirty = 2;
render_path_raytrace_ready = false;
}
void layers_new_layer_clear(slot_layer_t *l) {
slot_layer_clear(l, 0x00000000, NULL, 1.0, layers_default_rough, 0.0);
}
slot_layer_t *layers_new_layer(bool clear, i32 position, slot_layer_t *parent) {
if (g_project->_->layers->length > layers_max_layers) {
return NULL;
}
slot_layer_t *l = slot_layer_create("", LAYER_SLOT_TYPE_LAYER, parent);
l->object_mask = g_context->layer_filter;
if (position == -1 && slot_layer_is_filter(l)) {
position = array_index_of(g_project->_->layers, parent);
}
if (position == -1) {
if (slot_layer_is_mask(g_context->layer) || slot_layer_is_filter(g_context->layer))
context_set_layer(g_context->layer->parent);
array_insert(g_project->_->layers, array_index_of(g_project->_->layers, g_context->layer) + 1, l);
}
else {
array_insert(g_project->_->layers, position, l);
}
context_set_layer(l);
if (parent == NULL) {
i32 li = array_index_of(g_project->_->layers, g_context->layer);
if (li > 0) {
slot_layer_t *below = g_project->_->layers->buffer[li - 1];
if (slot_layer_is_layer(below)) {
g_context->layer->parent = below->parent;
}
}
}
if (clear) {
sys_notify_on_next_frame(&layers_new_layer_clear, l);
}
stage_t *stage = tab_stages_get_stage();
if (stage != NULL && string_array_index_of(stage->layers, l->name) < 0) {
string_array_push(stage->layers, l->name);
}
g_context->layer_preview_dirty = true;
return l;
}
void layers_new_mask_clear(slot_layer_t *l) {
slot_layer_clear(l, 0x00000000, NULL, 1.0, layers_default_rough, 0.0);
}
slot_layer_t *layers_new_mask(bool clear, slot_layer_t *parent, i32 position) {
if (g_project->_->layers->length > layers_max_layers) {
return NULL;
}
slot_layer_t *l = slot_layer_create("", LAYER_SLOT_TYPE_MASK, parent);
if (position == -1) {
position = array_index_of(g_project->_->layers, parent);
}
array_insert(g_project->_->layers, position, l);
context_set_layer(l);
if (clear) {
sys_notify_on_next_frame(&layers_new_mask_clear, l);
}
g_context->layer_preview_dirty = true;
return l;
}
slot_layer_t *layers_new_group() {
if (g_project->_->layers->length > layers_max_layers) {
return NULL;
}
slot_layer_t *l = slot_layer_create("", LAYER_SLOT_TYPE_GROUP, NULL);
any_array_push(g_project->_->layers, l);
context_set_layer(l);
return l;
}
static slot_layer_t *_layers_path_sculpt_layer = NULL;
void layers_new_path_layer_sculpt_init(void *_) {
sculpt_init();
sculpt_init_sculpt_texture(_layers_path_sculpt_layer);
}
slot_layer_t *layers_new_path_layer(bool curved) {
slot_layer_t *l = layers_new_layer(true, -1, NULL);
if (l == NULL) {
return NULL;
}
l->path_points = f32_array_create(0);
l->path_points_world = f32_array_create(0);
l->path_points_camera = f32_array_create(0);
l->path_points_parent = i32_array_create(0);
l->path_tool = -1;
l->path_curved = curved;
l->path_material = g_context->material;
l->name = string(curved ? "Curve %d" : "Path %d", l->id + 1);
if (g_config->workflow == WORKFLOW_SCULPT) {
_layers_path_sculpt_layer = l;
sys_notify_on_next_frame(&layers_new_path_layer_sculpt_init, NULL);
}
return l;
}
void layers_create_fill_layer_on_next_frame(void *_) {
slot_layer_t *l = layers_new_layer(false, _layers_position, NULL);
history_new_layer();
l->uv_type = _layers_uv_type;
if (!mat4_isnan(_layers_decal_mat)) {
l->decal_mat = _layers_decal_mat;
}
l->object_mask = g_context->layer_filter;
if (g_config->workflow == WORKFLOW_SCULPT) {
sculpt_init();
sculpt_init_sculpt_texture(l);
}
history_to_fill_layer();
slot_layer_to_fill_layer(l);
}
void layers_create_fill_layer(uv_type_t uv_type, mat4_t decal_mat, i32 position) {
// if (g_context->tool == TOOL_TYPE_CURSOR) {
// return;
// }
_layers_uv_type = uv_type;
_layers_decal_mat = decal_mat;
_layers_position = position;
sys_notify_on_next_frame(&layers_create_fill_layer_on_next_frame, NULL);
}
void tab_materials_button_new_on_next_frame(void *_);
void layers_create_filter_on_next_frame(void *_) {
if (slot_layer_get_filters(g_context->layer, true) != NULL) {
return;
}
tab_materials_button_new_on_next_frame(NULL);
slot_layer_t *l = layers_new_layer(false, -1, g_context->layer);
history_new_layer();
history_to_fill_layer();
slot_layer_to_fill_layer(l);
// Filter material
g_context->material->canvas->name = string_copy(g_context->layer->name);
ui_nodes_t *nodes = g_context->material->nodes;
ui_node_canvas_t *canvas = g_context->material->canvas;
ui_node_t *nout = NULL;
for (i32 i = 0; i < canvas->nodes->length; ++i) {
ui_node_t *n = canvas->nodes->buffer[i];
if (string_equals(n->type, "OUTPUT_MATERIAL_PBR")) {
nout = n;
break;
}
}
for (i32 i = 0; i < canvas->nodes->length; ++i) {
ui_node_t *n = canvas->nodes->buffer[i];
if (string_equals(n->name, "Color")) {
ui_remove_node(n, canvas);
break;
}
}
ui_node_t *n = nodes_material_create_node("LAYER", NULL);
n->buttons->buffer[0]->default_value = f32_array_create_x(array_index_of(g_project->_->layers, l->parent));
n->x = -50;
n->y = 100;
for (int i = 0; i < 9; ++i) {
ui_node_link_t *l =
GC_ALLOC_INIT(ui_node_link_t, {.id = ui_next_link_id(canvas->links), .from_id = n->id, .from_socket = i, .to_id = nout->id, .to_socket = i});
any_array_push(canvas->links, l);
}
layers_update_fill_layer(true);
util_render_make_material_preview();
}
void layers_create_filter() {
sys_notify_on_next_frame(&layers_create_filter_on_next_frame, NULL);
}
void layers_create_image_mask(asset_t *asset) {
slot_layer_t *l = g_context->layer;
if (slot_layer_is_mask(l) || slot_layer_is_group(l)) {
return;
}
history_new_layer();
slot_layer_t *m = layers_new_mask(false, l, -1);
slot_layer_clear(m, 0x00000000, project_get_image(asset), 1.0, layers_default_rough, 0.0);
g_context->layer_preview_dirty = true;
}
void layers_create_image_layer(asset_t *asset) {
history_new_layer();
slot_layer_t *m = layers_new_layer(false, -1, NULL);
slot_layer_clear(m, 0x00000000, project_get_image(asset), 1.0, layers_default_rough, 0.0);
g_context->layer_preview_dirty = true;
}
void layers_create_color_layer_on_next_frame(void *_) {
slot_layer_t *l = layers_new_layer(false, _layers_position, NULL);
history_new_layer();
l->uv_type = UV_TYPE_UVMAP;
l->object_mask = g_context->layer_filter;
slot_layer_clear(l, _layers_base_color, NULL, _layers_occlusion, _layers_roughness, _layers_metallic);
}
void layers_create_color_layer(i32 base_color, f32 occlusion, f32 roughness, f32 metallic, i32 position) {
_layers_base_color = base_color;
_layers_occlusion = occlusion;
_layers_roughness = roughness;
_layers_metallic = metallic;
_layers_position = position;
sys_notify_on_next_frame(&layers_create_color_layer_on_next_frame, NULL);
}
void layers_duplicate_layer(slot_layer_t *l) {
if (!slot_layer_is_group(l)) {
slot_layer_t *new_layer = slot_layer_duplicate(l);
context_set_layer(new_layer);
slot_layer_t_array_t *masks = slot_layer_get_masks(l, false);
if (masks != NULL) {
for (i32 i = 0; i < masks->length; ++i) {
slot_layer_t *m = masks->buffer[i];
m = slot_layer_duplicate(m);
m->parent = new_layer;
array_remove(g_project->_->layers, m);
array_insert(g_project->_->layers, array_index_of(g_project->_->layers, new_layer), m);
}
}
context_set_layer(new_layer);
}
else {
slot_layer_t *new_group = layers_new_group();
array_remove(g_project->_->layers, new_group);
array_insert(g_project->_->layers, array_index_of(g_project->_->layers, l) + 1, new_group);
// group.show_panel = true;
for (i32 i = 0; i < slot_layer_get_children(l)->length; ++i) {
slot_layer_t *c = slot_layer_get_children(l)->buffer[i];
slot_layer_t_array_t *masks = slot_layer_get_masks(c, false);
slot_layer_t *new_layer = slot_layer_duplicate(c);
new_layer->parent = new_group;
array_remove(g_project->_->layers, new_layer);
array_insert(g_project->_->layers, array_index_of(g_project->_->layers, new_group), new_layer);
if (masks != NULL) {
for (i32 i = 0; i < masks->length; ++i) {
slot_layer_t *m = masks->buffer[i];
slot_layer_t *new_mask = slot_layer_duplicate(m);
new_mask->parent = new_layer;
array_remove(g_project->_->layers, new_mask);
array_insert(g_project->_->layers, array_index_of(g_project->_->layers, new_layer), new_mask);
}
}
}
slot_layer_t_array_t *group_masks = slot_layer_get_masks(l, true);
if (group_masks != NULL) {
for (i32 i = 0; i < group_masks->length; ++i) {
slot_layer_t *m = group_masks->buffer[i];
slot_layer_t *new_mask = slot_layer_duplicate(m);
new_mask->parent = new_group;
array_remove(g_project->_->layers, new_mask);
array_insert(g_project->_->layers, array_index_of(g_project->_->layers, new_group), new_mask);
}
}
context_set_layer(new_group);
}
}
void layers_apply_masks(slot_layer_t *l) {
slot_layer_t_array_t *masks = slot_layer_get_masks(l, true);
if (masks != NULL) {
for (i32 i = 0; i < masks->length - 1; ++i) {
layers_merge_layer(masks->buffer[i + 1], masks->buffer[i], false);
slot_layer_delete(masks->buffer[i]);
}
slot_layer_apply_mask(masks->buffer[masks->length - 1]);
g_context->layer_preview_dirty = true;
}
}
void layers_merge_down() {
slot_layer_t *l1 = g_context->layer;
if (slot_layer_is_group(l1)) {
l1 = layers_merge_group(l1);
}
else if (slot_layer_has_masks(l1, true)) { // It is a layer
layers_apply_masks(l1);
context_set_layer(l1);
}
slot_layer_t *l0 = g_project->_->layers->buffer[array_index_of(g_project->_->layers, l1) - 1];
if (slot_layer_is_group(l0)) {
l0 = layers_merge_group(l0);
}
else if (slot_layer_has_masks(l0, true)) { // It is a layer
layers_apply_masks(l0);
context_set_layer(l0);
}
layers_merge_layer(l0, l1, false);
slot_layer_delete(l1);
context_set_layer(l0);
g_context->layer_preview_dirty = true;
}
slot_layer_t *layers_merge_group(slot_layer_t *l) {
if (!slot_layer_is_group(l)) {
return NULL;
}
slot_layer_t_array_t *children = slot_layer_get_children(l);
if (children->length == 1 && slot_layer_has_masks(children->buffer[0], false)) {
layers_apply_masks(children->buffer[0]);
}
for (i32 i = 0; i < children->length - 1; ++i) {
context_set_layer(children->buffer[children->length - 1 - i]);
history_merge_layers();
layers_merge_down();
}
// Now apply the group masks
slot_layer_t_array_t *masks = slot_layer_get_masks(l, true);
if (masks != NULL) {
for (i32 i = 0; i < masks->length - 1; ++i) {
layers_merge_layer(masks->buffer[i + 1], masks->buffer[i], false);
slot_layer_delete(masks->buffer[i]);
}
layers_apply_mask(children->buffer[0], masks->buffer[masks->length - 1]);
}
children->buffer[0]->parent = NULL;
children->buffer[0]->name = l->name;
if (children->buffer[0]->fill_material != NULL) {
slot_layer_to_paint_layer(children->buffer[0]);
}
slot_layer_delete(l);
return children->buffer[0];
}
void layers_merge_layer(slot_layer_t *l0, slot_layer_t *l1, bool use_mask) {
if (!l1->visible || slot_layer_is_group(l1)) {
return;
}
layers_make_temp_img();
draw_begin(layers_temp_image, false, 0); // Copy to temp
draw_set_pipeline(pipes_copy);
draw_image(l0->texpaint, 0, 0);
draw_set_pipeline(NULL);
draw_end();
render_target_t *empty_rt = any_map_get(render_path_render_targets, "empty_white");
gpu_texture_t *empty = empty_rt->_image;
gpu_texture_t *mask = empty;
slot_layer_t_array_t *l1masks = use_mask ? slot_layer_get_masks(l1, true) : NULL;
if (l1masks != NULL) {
// for (let i: i32 = 1; i < l1masks.length - 1; ++i) {
// merge_layer(l1masks[i + 1], l1masks[i]);
// }
layers_make_temp_mask_img();
_gpu_begin(pipes_temp_mask_image, NULL, NULL, GPU_CLEAR_NONE, 0, 0.0);
gpu_set_pipeline(pipes_merge_mask);
gpu_set_texture(pipes_tex0_merge_mask, l1masks->buffer[0]->texpaint);
gpu_set_texture(pipes_texa_merge_mask, empty);
gpu_set_float(pipes_opac_merge_mask, l1masks->buffer[0]->mask_opacity);
gpu_set_int(pipes_blending_merge_mask, BLEND_TYPE_MIX);
gpu_set_vertex_buffer(const_data_screen_aligned_vb);
gpu_set_index_buffer(const_data_screen_aligned_ib);
gpu_draw();
gpu_end();
mask = pipes_temp_mask_image;
}
if (slot_layer_is_mask(l1)) {
_gpu_begin(l0->texpaint, NULL, NULL, GPU_CLEAR_NONE, 0, 0.0);
gpu_set_pipeline(pipes_merge_mask);
gpu_set_texture(pipes_tex0_merge_mask, l1->texpaint);
gpu_set_texture(pipes_texa_merge_mask, layers_temp_image);
gpu_set_float(pipes_opac_merge_mask, slot_layer_get_opacity(l1));
gpu_set_int(pipes_blending_merge_mask, BLEND_TYPE_MIX);
gpu_set_vertex_buffer(const_data_screen_aligned_vb);
gpu_set_index_buffer(const_data_screen_aligned_ib);
gpu_draw();
gpu_end();
}
if (slot_layer_is_layer(l1)) {
if (l1->paint_base) {
_gpu_begin(l0->texpaint, NULL, NULL, GPU_CLEAR_NONE, 0, 0.0);
gpu_set_pipeline(pipes_merge);
gpu_set_texture(pipes_tex0, l1->texpaint);
gpu_set_texture(pipes_tex1, empty);
gpu_set_texture(pipes_texmask, mask);
gpu_set_texture(pipes_texa, layers_temp_image);
gpu_set_float(pipes_opac, slot_layer_get_opacity(l1));
gpu_set_float(pipes_tex1w, empty->width);
gpu_set_int(pipes_blending, slot_layer_get_blending(l1));
gpu_set_vertex_buffer(const_data_screen_aligned_vb);
gpu_set_index_buffer(const_data_screen_aligned_ib);
gpu_draw();
gpu_end();
}
if (l0->texpaint_nor != NULL) {
draw_begin(layers_temp_image, false, 0);
draw_set_pipeline(pipes_copy);
draw_image(l0->texpaint_nor, 0, 0);
draw_set_pipeline(NULL);
draw_end();
if (l1->paint_nor) {
_gpu_begin(l0->texpaint_nor, NULL, NULL, GPU_CLEAR_NONE, 0, 0.0);
gpu_set_pipeline(pipes_merge);
gpu_set_texture(pipes_tex0, l1->texpaint);
gpu_set_texture(pipes_tex1, l1->texpaint_nor);
gpu_set_texture(pipes_texmask, mask);
gpu_set_texture(pipes_texa, layers_temp_image);
gpu_set_float(pipes_opac, slot_layer_get_opacity(l1));
gpu_set_float(pipes_tex1w, l1->texpaint_nor->width);
gpu_set_int(pipes_blending, l1->paint_nor_blend ? 102 : 101);
gpu_set_vertex_buffer(const_data_screen_aligned_vb);
gpu_set_index_buffer(const_data_screen_aligned_ib);
gpu_draw();
gpu_end();
}
}
if (l0->texpaint_pack != NULL) {
draw_begin(layers_temp_image, false, 0);
draw_set_pipeline(pipes_copy);
draw_image(l0->texpaint_pack, 0, 0);
draw_set_pipeline(NULL);
draw_end();
if (l1->paint_occ || l1->paint_rough || l1->paint_met || l1->paint_height) {
if (l1->paint_occ && l1->paint_rough && l1->paint_met && l1->paint_height) {
layers_commands_merge_pack(pipes_merge, l0->texpaint_pack, l1->texpaint, l1->texpaint_pack, slot_layer_get_opacity(l1), mask,
l1->paint_height_blend ? 103 : 101);
}
else {
if (l1->paint_occ) {
layers_commands_merge_pack(pipes_merge_r, l0->texpaint_pack, l1->texpaint, l1->texpaint_pack, slot_layer_get_opacity(l1), mask, 101);
}
if (l1->paint_rough) {
layers_commands_merge_pack(pipes_merge_g, l0->texpaint_pack, l1->texpaint, l1->texpaint_pack, slot_layer_get_opacity(l1), mask, 101);
}
if (l1->paint_met) {
layers_commands_merge_pack(pipes_merge_b, l0->texpaint_pack, l1->texpaint, l1->texpaint_pack, slot_layer_get_opacity(l1), mask, 101);
}
}
}
}
}
}
slot_layer_t *layers_flatten(bool height_to_normal, slot_layer_t_array_t *layers) {
if (layers == NULL) {
layers = g_project->_->layers;
}
layers_make_temp_img();
layers_make_export_img();
render_target_t *empty_rt = any_map_get(render_path_render_targets, "empty_white");
gpu_texture_t *empty = empty_rt->_image;
// Clear export layer
_gpu_begin(layers_expa, NULL, NULL, GPU_CLEAR_COLOR, color_from_floats(0.0, 0.0, 0.0, 0.0), 0.0);
gpu_end();
_gpu_begin(layers_expb, NULL, NULL, GPU_CLEAR_COLOR, color_from_floats(0.5, 0.5, 1.0, 0.0), 0.0);
gpu_end();
_gpu_begin(layers_expc, NULL, NULL, GPU_CLEAR_COLOR, color_from_floats(1.0, 0.0, 0.0, 0.0), 0.0);
gpu_end();
// Flatten layers
for (i32 i = 0; i < layers->length; ++i) {
slot_layer_t *l1 = layers->buffer[i];
if (!slot_layer_is_visible(l1)) {
continue;
}
if (!slot_layer_is_layer(l1)) {
continue;
}
if (slot_layer_get_filters(l1, false) != NULL) {
continue;
}
gpu_texture_t *mask = empty;
slot_layer_t_array_t *l1masks = slot_layer_get_masks(l1, true);
if (l1masks != NULL) {
layers_make_temp_mask_img();
draw_begin(pipes_temp_mask_image, GPU_CLEAR_COLOR, 0xffffffff);
draw_end();
slot_layer_t *l1 = GC_ALLOC_INIT(slot_layer_t, {.texpaint = pipes_temp_mask_image});
for (i32 i = 0; i < l1masks->length; ++i) {
layers_merge_layer(l1, l1masks->buffer[i], false);
}
mask = pipes_temp_mask_image;
}
if (l1->paint_base) {
draw_begin(layers_temp_image, false, 0); // Copy to temp
draw_set_pipeline(pipes_copy);
draw_image(layers_expa, 0, 0);
draw_set_pipeline(NULL);
draw_end();
// if (g_context->tool == TOOL_TYPE_CURSOR) {
// // Do not multiply basecol by alpha
// draw_begin(layers_expa, false, 0); // Copy to temp
// draw_set_pipeline(pipes_copy);
// draw_image(l1->texpaint, 0, 0);
// draw_set_pipeline(NULL);
// draw_end();
// }
// else {
_gpu_begin(layers_expa, NULL, NULL, GPU_CLEAR_NONE, 0, 0.0);
gpu_set_pipeline(pipes_merge);
gpu_set_texture(pipes_tex0, l1->texpaint);
gpu_set_texture(pipes_tex1, empty);
gpu_set_texture(pipes_texmask, mask);
gpu_set_texture(pipes_texa, layers_temp_image);
gpu_set_float(pipes_opac, slot_layer_get_opacity(l1));
gpu_set_float(pipes_tex1w, empty->width);
gpu_set_int(pipes_blending, layers->length > 1 ? slot_layer_get_blending(l1) : 0);
gpu_set_vertex_buffer(const_data_screen_aligned_vb);
gpu_set_index_buffer(const_data_screen_aligned_ib);
gpu_draw();
gpu_end();
// }
}
if (l1->paint_nor) {
draw_begin(layers_temp_image, false, 0);
draw_set_pipeline(pipes_copy);
draw_image(layers_expb, 0, 0);
draw_set_pipeline(NULL);
draw_end();
_gpu_begin(layers_expb, NULL, NULL, GPU_CLEAR_NONE, 0, 0.0);
gpu_set_pipeline(pipes_merge);
gpu_set_texture(pipes_tex0, l1->texpaint);
gpu_set_texture(pipes_tex1, l1->texpaint_nor);
gpu_set_texture(pipes_texmask, mask);
gpu_set_texture(pipes_texa, layers_temp_image);
gpu_set_float(pipes_opac, slot_layer_get_opacity(l1));
gpu_set_float(pipes_tex1w, l1->texpaint_nor->width);
gpu_set_int(pipes_blending, l1->paint_nor_blend ? 102 : 101);
gpu_set_vertex_buffer(const_data_screen_aligned_vb);
gpu_set_index_buffer(const_data_screen_aligned_ib);
gpu_draw();
gpu_end();
}
if (l1->paint_occ || l1->paint_rough || l1->paint_met || l1->paint_height) {
draw_begin(layers_temp_image, false, 0);
draw_set_pipeline(pipes_copy);
draw_image(layers_expc, 0, 0);
draw_set_pipeline(NULL);
draw_end();
if (l1->paint_occ && l1->paint_rough && l1->paint_met && l1->paint_height) {
layers_commands_merge_pack(pipes_merge, layers_expc, l1->texpaint, l1->texpaint_pack, slot_layer_get_opacity(l1), mask,
l1->paint_height_blend ? 103 : 101);
}
else {
if (l1->paint_occ) {
layers_commands_merge_pack(pipes_merge_r, layers_expc, l1->texpaint, l1->texpaint_pack, slot_layer_get_opacity(l1), mask, 101);
}
if (l1->paint_rough) {
layers_commands_merge_pack(pipes_merge_g, layers_expc, l1->texpaint, l1->texpaint_pack, slot_layer_get_opacity(l1), mask, 101);
}
if (l1->paint_met) {
layers_commands_merge_pack(pipes_merge_b, layers_expc, l1->texpaint, l1->texpaint_pack, slot_layer_get_opacity(l1), mask, 101);
}
}
}
}
slot_layer_t *l0 = GC_ALLOC_INIT(slot_layer_t, {.texpaint = layers_expa, .texpaint_nor = layers_expb, .texpaint_pack = layers_expc});
// Merge height map into normal map
if (height_to_normal && make_material_height_used) {
draw_begin(layers_temp_image, false, 0);
draw_set_pipeline(pipes_copy);
draw_image(l0->texpaint_nor, 0, 0);
draw_set_pipeline(NULL);
draw_end();
_gpu_begin(l0->texpaint_nor, NULL, NULL, GPU_CLEAR_NONE, 0, 0.0);
gpu_set_pipeline(pipes_merge);
gpu_set_texture(pipes_tex0, layers_temp_image);
gpu_set_texture(pipes_tex1, l0->texpaint_pack);
gpu_set_texture(pipes_texmask, empty);
gpu_set_texture(pipes_texa, empty);
gpu_set_float(pipes_opac, 1.0);
gpu_set_float(pipes_tex1w, l0->texpaint_pack->width);
gpu_set_int(pipes_blending, 104);
gpu_set_vertex_buffer(const_data_screen_aligned_vb);
gpu_set_index_buffer(const_data_screen_aligned_ib);
gpu_draw();
gpu_end();
}
return l0;
}
void layers_on_resized_on_next_frame(void *_) {
layers_resize();
slot_layer_t *_layer = g_context->layer;
slot_material_t *_material = g_context->material;
for (i32 i = 0; i < g_project->_->layers->length; ++i) {
slot_layer_t *l = g_project->_->layers->buffer[i];
if (l->fill_material != NULL) {
g_context->layer = l;
g_context->material = l->fill_material;
layers_update_fill_layer(true);
}
}
g_context->layer = _layer;
g_context->material = _material;
make_material_parse_paint_material(true);
}
void layers_on_resized() {
sys_notify_on_next_frame(&layers_on_resized_on_next_frame, NULL);
gc_unroot(util_uv_uvmap);
util_uv_uvmap = NULL;
util_uv_uvmap_cached = false;
gc_unroot(util_uv_trianglemap);
util_uv_trianglemap = NULL;
util_uv_trianglemap_cached = false;
util_uv_dilatemap_cached = false;
render_path_raytrace_ready = false;
}
void tab_layers_remap_layer_pointers(ui_node_t_array_t *nodes, i32_imap_t *pointer_map) {
for (i32 i = 0; i < nodes->length; ++i) {
ui_node_t *n = nodes->buffer[i];
if (string_equals(n->type, "LAYER") || string_equals(n->type, "LAYER_MASK")) {
i32 i = n->buttons->buffer[0]->default_value->buffer[0];
if (i32_imap_get(pointer_map, i) != -1) {
n->buttons->buffer[0]->default_value->buffer[0] = i32_imap_get(pointer_map, i);
}
}
}
}
i32_map_t *tab_layers_init_layer_map() {
i32_map_t *res = any_map_create();
for (i32 i = 0; i < g_project->_->layers->length; ++i) {
i32_map_set(res, g_project->_->layers->buffer[i], i);
}
return res;
}
i32_imap_t *tab_layers_fill_layer_map(i32_map_t *map) {
i32_imap_t *res = any_map_create();
string_array_t *keys = map_keys(map);
for (i32 i = 0; i < keys->length; ++i) {
char *l = keys->buffer[i];
i32_imap_set(res, i32_map_get(map, l), array_index_of(g_project->_->layers, l) > -1 ? array_index_of(g_project->_->layers, l) : 9999);
}
return res;
}
void tab_layers_make_mask_preview_rgba32_on_next_frame(void *_) {
slot_layer_t *l = tab_layers_l;
draw_begin(g_context->mask_preview_rgba32, true, 0xff000000);
draw_set_pipeline(ui_view2d_pipe);
gpu_set_int(ui_view2d_channel_loc, 1);
draw_image(l->texpaint_preview, 0, 0);
draw_end();
draw_set_pipeline(NULL);
}
void tab_layers_make_mask_preview_rgba32(slot_layer_t *l) {
if (g_context->mask_preview_rgba32 == NULL) {
g_context->mask_preview_rgba32 = gpu_create_render_target(util_render_layer_preview_size, util_render_layer_preview_size, GPU_TEXTURE_FORMAT_RGBA32);
}
// Convert from R8 to RGBA32 for tooltip display
if (g_context->mask_preview_last != l) {
g_context->mask_preview_last = l;
gc_unroot(tab_layers_l);
tab_layers_l = l;
gc_root(tab_layers_l);
sys_notify_on_next_frame(&tab_layers_make_mask_preview_rgba32_on_next_frame, NULL);
}
}