/** * @file lv_draw_sdl_composite.c * */ /********************* * INCLUDES *********************/ #include "../../lv_conf_internal.h" #if LV_USE_GPU_SDL #include "../../misc/lv_gc.h" #include "../../core/lv_refr.h" #include "lv_draw_sdl_composite.h" #include "lv_draw_sdl_utils.h" #include "lv_draw_sdl_priv.h" #include "lv_draw_sdl_texture_cache.h" /********************* * DEFINES *********************/ /********************** * TYPEDEFS **********************/ typedef struct { lv_sdl_cache_key_magic_t magic; lv_draw_sdl_composite_texture_id_t type; } composite_key_t; /********************** * STATIC PROTOTYPES **********************/ static composite_key_t mask_key_create(lv_draw_sdl_composite_texture_id_t type); static lv_coord_t next_pow_of_2(lv_coord_t num); static void dump_masks(SDL_Texture * texture, const lv_area_t * coords); /********************** * STATIC VARIABLES **********************/ /********************** * MACROS **********************/ /********************** * GLOBAL FUNCTIONS **********************/ bool lv_draw_sdl_composite_begin(lv_draw_sdl_ctx_t * ctx, const lv_area_t * coords_in, const lv_area_t * clip_in, const lv_area_t * extension, lv_blend_mode_t blend_mode, lv_area_t * coords_out, lv_area_t * clip_out, lv_area_t * apply_area) { lv_area_t full_coords = *coords_in; /* Normalize full_coords */ if(full_coords.x1 > full_coords.x2) { lv_coord_t x2 = full_coords.x2; full_coords.x2 = full_coords.x1; full_coords.x1 = x2; } if(full_coords.y1 > full_coords.y2) { lv_coord_t y2 = full_coords.y2; full_coords.y2 = full_coords.y1; full_coords.y1 = y2; } if(extension) { full_coords.x1 -= extension->x1; full_coords.x2 += extension->x2; full_coords.y1 -= extension->y1; full_coords.y2 += extension->y2; } if(!_lv_area_intersect(apply_area, &full_coords, clip_in)) return false; bool has_mask = lv_draw_mask_is_any(apply_area); const bool draw_mask = has_mask && LV_GPU_SDL_CUSTOM_BLEND_MODE; const bool draw_blend = blend_mode != LV_BLEND_MODE_NORMAL; if(draw_mask || draw_blend) { lv_draw_sdl_context_internals_t * internals = ctx->internals; LV_ASSERT(internals->mask == NULL && internals->composition == NULL && internals->target_backup == NULL); lv_coord_t w = lv_area_get_width(apply_area), h = lv_area_get_height(apply_area); internals->composition = lv_draw_sdl_composite_texture_obtain(ctx, LV_DRAW_SDL_COMPOSITE_TEXTURE_ID_TARGET0, w, h, &internals->composition_cached); /* Don't need to worry about integral overflow */ lv_coord_t ofs_x = (lv_coord_t) - apply_area->x1, ofs_y = (lv_coord_t) - apply_area->y1; /* Offset draw area to start with (0,0) of coords */ lv_area_move(coords_out, ofs_x, ofs_y); lv_area_move(clip_out, ofs_x, ofs_y); internals->target_backup = SDL_GetRenderTarget(ctx->renderer); SDL_SetRenderTarget(ctx->renderer, internals->composition); SDL_SetRenderDrawColor(ctx->renderer, 255, 255, 255, 0); /* SDL_RenderClear is not working properly, so we overwrite the target with solid color */ SDL_SetRenderDrawBlendMode(ctx->renderer, SDL_BLENDMODE_NONE); SDL_RenderFillRect(ctx->renderer, NULL); SDL_SetRenderDrawBlendMode(ctx->renderer, SDL_BLENDMODE_BLEND); #if LV_GPU_SDL_CUSTOM_BLEND_MODE internals->mask = lv_draw_sdl_composite_texture_obtain(ctx, LV_DRAW_SDL_COMPOSITE_TEXTURE_ID_STREAM0, w, h, &internals->composition_cached); dump_masks(internals->mask, apply_area); #endif } else if(has_mask) { /* Fallback mask handling. This will at least make bars looks less bad */ for(uint8_t i = 0; i < _LV_MASK_MAX_NUM; i++) { _lv_draw_mask_common_dsc_t * comm_param = LV_GC_ROOT(_lv_draw_mask_list[i]).param; if(comm_param == NULL) continue; switch(comm_param->type) { case LV_DRAW_MASK_TYPE_RADIUS: { const lv_draw_mask_radius_param_t * param = (const lv_draw_mask_radius_param_t *) comm_param; if(param->cfg.outer) break; _lv_area_intersect(clip_out, apply_area, ¶m->cfg.rect); break; } default: break; } } } return has_mask; } void lv_draw_sdl_composite_end(lv_draw_sdl_ctx_t * ctx, const lv_area_t * apply_area, lv_blend_mode_t blend_mode) { lv_draw_sdl_context_internals_t * internals = ctx->internals; SDL_Rect src_rect = {0, 0, lv_area_get_width(apply_area), lv_area_get_height(apply_area)}; #if LV_GPU_SDL_CUSTOM_BLEND_MODE if(internals->mask) { SDL_BlendMode mode = SDL_ComposeCustomBlendMode(SDL_BLENDFACTOR_ZERO, SDL_BLENDFACTOR_ONE, SDL_BLENDOPERATION_ADD, SDL_BLENDFACTOR_ZERO, SDL_BLENDFACTOR_SRC_ALPHA, SDL_BLENDOPERATION_ADD); SDL_SetTextureBlendMode(internals->mask, mode); SDL_RenderCopy(ctx->renderer, internals->mask, &src_rect, &src_rect); } #endif /* Shapes are drawn on composite layer when mask or blend mode is present */ if(internals->composition) { SDL_Rect dst_rect; lv_area_to_sdl_rect(apply_area, &dst_rect); SDL_SetRenderTarget(ctx->renderer, internals->target_backup); switch(blend_mode) { case LV_BLEND_MODE_NORMAL: SDL_SetTextureBlendMode(internals->composition, SDL_BLENDMODE_BLEND); break; case LV_BLEND_MODE_ADDITIVE: SDL_SetTextureBlendMode(internals->composition, SDL_BLENDMODE_ADD); break; #if LV_GPU_SDL_CUSTOM_BLEND_MODE case LV_BLEND_MODE_SUBTRACTIVE: { SDL_BlendMode mode = SDL_ComposeCustomBlendMode(SDL_BLENDFACTOR_ONE, SDL_BLENDFACTOR_ONE, SDL_BLENDOPERATION_SUBTRACT, SDL_BLENDFACTOR_ONE, SDL_BLENDFACTOR_ONE, SDL_BLENDOPERATION_SUBTRACT); SDL_SetTextureBlendMode(internals->composition, mode); break; } case LV_BLEND_MODE_MULTIPLY: { SDL_BlendMode mode = SDL_ComposeCustomBlendMode(SDL_BLENDFACTOR_ZERO, SDL_BLENDFACTOR_SRC_COLOR, SDL_BLENDOPERATION_ADD, SDL_BLENDFACTOR_ZERO, SDL_BLENDFACTOR_DST_ALPHA, SDL_BLENDOPERATION_ADD); SDL_SetTextureBlendMode(internals->composition, mode); break; } #endif default: LV_LOG_WARN("Doesn't support blend mode %d", blend_mode); SDL_SetTextureBlendMode(internals->composition, SDL_BLENDMODE_BLEND); /* Unsupported yet */ break; } SDL_RenderCopy(ctx->renderer, internals->composition, &src_rect, &dst_rect); if(!internals->composition_cached) { LV_LOG_WARN("Texture is not cached, this will impact performance."); SDL_DestroyTexture(internals->composition); } } internals->mask = internals->composition = internals->target_backup = NULL; } SDL_Texture * lv_draw_sdl_composite_texture_obtain(lv_draw_sdl_ctx_t * ctx, lv_draw_sdl_composite_texture_id_t id, lv_coord_t w, lv_coord_t h, bool * texture_in_cache) { lv_point_t * tex_size = NULL; composite_key_t mask_key = mask_key_create(id); SDL_Texture * result = lv_draw_sdl_texture_cache_get_with_userdata(ctx, &mask_key, sizeof(composite_key_t), NULL, (void **) &tex_size); if(result == NULL || tex_size->x < w || tex_size->y < h) { lv_coord_t size = next_pow_of_2(LV_MAX(w, h)); int access = SDL_TEXTUREACCESS_STREAMING; if(id >= LV_DRAW_SDL_COMPOSITE_TEXTURE_ID_TRANSFORM0) { access = SDL_TEXTUREACCESS_TARGET; } else if(id >= LV_DRAW_SDL_COMPOSITE_TEXTURE_ID_TARGET0) { access = SDL_TEXTUREACCESS_TARGET; } result = SDL_CreateTexture(ctx->renderer, LV_DRAW_SDL_TEXTURE_FORMAT, access, size, size); tex_size = lv_mem_alloc(sizeof(lv_point_t)); tex_size->x = tex_size->y = size; bool in_cache = lv_draw_sdl_texture_cache_put_advanced(ctx, &mask_key, sizeof(composite_key_t), result, tex_size, lv_mem_free, 0); if(!in_cache) { lv_mem_free(tex_size); } if(texture_in_cache != NULL) { *texture_in_cache = in_cache; } } else if(texture_in_cache != NULL) { *texture_in_cache = true; } return result; } /********************** * STATIC FUNCTIONS **********************/ static composite_key_t mask_key_create(lv_draw_sdl_composite_texture_id_t type) { composite_key_t key; /* VERY IMPORTANT! Padding between members is uninitialized, so we have to wipe them manually */ SDL_memset(&key, 0, sizeof(key)); key.magic = LV_GPU_CACHE_KEY_MAGIC_MASK; key.type = type; return key; } static lv_coord_t next_pow_of_2(lv_coord_t num) { lv_coord_t n = 128; while(n < num && n < 16384) { n = n << 1; } return n; } static void dump_masks(SDL_Texture * texture, const lv_area_t * coords) { lv_coord_t w = lv_area_get_width(coords), h = lv_area_get_height(coords); SDL_assert(w > 0 && h > 0); SDL_Rect rect = {0, 0, w, h}; uint8_t * pixels; int pitch; if(SDL_LockTexture(texture, &rect, (void **) &pixels, &pitch) != 0) return; lv_opa_t * line_buf = lv_mem_buf_get(rect.w); for(lv_coord_t y = 0; y < rect.h; y++) { lv_memset_ff(line_buf, rect.w); lv_coord_t abs_x = (lv_coord_t) coords->x1, abs_y = (lv_coord_t)(y + coords->y1), len = (lv_coord_t) rect.w; lv_draw_mask_res_t res; res = lv_draw_mask_apply(line_buf, abs_x, abs_y, len); if(res == LV_DRAW_MASK_RES_TRANSP) { lv_memset_00(&pixels[y * pitch], 4 * rect.w); } else if(res == LV_DRAW_MASK_RES_FULL_COVER) { lv_memset_ff(&pixels[y * pitch], 4 * rect.w); } else { for(int x = 0; x < rect.w; x++) { const size_t idx = y * pitch + x * 4; pixels[idx] = line_buf[x]; pixels[idx + 1] = pixels[idx + 2] = pixels[idx + 3] = 0xFF; } } } lv_mem_buf_release(line_buf); SDL_UnlockTexture(texture); } #endif /*LV_USE_GPU_SDL*/