1 /**
2  * @file lv_draw_sdl_composite.c
3  *
4  */
5 
6 /*********************
7  *      INCLUDES
8  *********************/
9 #include "../../lv_conf_internal.h"
10 
11 #if LV_USE_GPU_SDL
12 
13 #include "../../misc/lv_gc.h"
14 #include "../../core/lv_refr.h"
15 #include "lv_draw_sdl_composite.h"
16 #include "lv_draw_sdl_utils.h"
17 #include "lv_draw_sdl_priv.h"
18 #include "lv_draw_sdl_texture_cache.h"
19 
20 /*********************
21  *      DEFINES
22  *********************/
23 
24 /**********************
25  *      TYPEDEFS
26  **********************/
27 
28 typedef struct {
29     lv_sdl_cache_key_magic_t magic;
30     lv_draw_sdl_composite_texture_id_t type;
31 } composite_key_t;
32 
33 /**********************
34  *  STATIC PROTOTYPES
35  **********************/
36 
37 static composite_key_t mask_key_create(lv_draw_sdl_composite_texture_id_t type);
38 
39 static lv_coord_t next_pow_of_2(lv_coord_t num);
40 
41 static void dump_masks(SDL_Texture * texture, const lv_area_t * coords);
42 /**********************
43  *  STATIC VARIABLES
44  **********************/
45 
46 /**********************
47  *      MACROS
48  **********************/
49 
50 /**********************
51  *   GLOBAL FUNCTIONS
52  **********************/
53 
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)54 bool lv_draw_sdl_composite_begin(lv_draw_sdl_ctx_t * ctx, const lv_area_t * coords_in, const lv_area_t * clip_in,
55                                  const lv_area_t * extension, lv_blend_mode_t blend_mode, lv_area_t * coords_out,
56                                  lv_area_t * clip_out, lv_area_t * apply_area)
57 {
58     lv_area_t full_coords = *coords_in;
59 
60     /* Normalize full_coords */
61     if(full_coords.x1 > full_coords.x2) {
62         lv_coord_t x2 = full_coords.x2;
63         full_coords.x2 = full_coords.x1;
64         full_coords.x1 = x2;
65     }
66     if(full_coords.y1 > full_coords.y2) {
67         lv_coord_t y2 = full_coords.y2;
68         full_coords.y2 = full_coords.y1;
69         full_coords.y1 = y2;
70     }
71 
72     if(extension) {
73         full_coords.x1 -= extension->x1;
74         full_coords.x2 += extension->x2;
75         full_coords.y1 -= extension->y1;
76         full_coords.y2 += extension->y2;
77     }
78 
79     if(!_lv_area_intersect(apply_area, &full_coords, clip_in)) return false;
80     bool has_mask = lv_draw_mask_is_any(apply_area);
81 
82     const bool draw_mask = has_mask && LV_GPU_SDL_CUSTOM_BLEND_MODE;
83     const bool draw_blend = blend_mode != LV_BLEND_MODE_NORMAL;
84     if(draw_mask || draw_blend) {
85         lv_draw_sdl_context_internals_t * internals = ctx->internals;
86         LV_ASSERT(internals->mask == NULL && internals->composition == NULL && internals->target_backup == NULL);
87 
88         lv_coord_t w = lv_area_get_width(apply_area), h = lv_area_get_height(apply_area);
89         internals->composition = lv_draw_sdl_composite_texture_obtain(ctx, LV_DRAW_SDL_COMPOSITE_TEXTURE_ID_TARGET0, w, h,
90                                                                       &internals->composition_cached);
91         /* Don't need to worry about integral overflow */
92         lv_coord_t ofs_x = (lv_coord_t) - apply_area->x1, ofs_y = (lv_coord_t) - apply_area->y1;
93         /* Offset draw area to start with (0,0) of coords */
94         lv_area_move(coords_out, ofs_x, ofs_y);
95         lv_area_move(clip_out, ofs_x, ofs_y);
96         internals->target_backup = SDL_GetRenderTarget(ctx->renderer);
97         SDL_SetRenderTarget(ctx->renderer, internals->composition);
98         SDL_SetRenderDrawColor(ctx->renderer, 255, 255, 255, 0);
99         /* SDL_RenderClear is not working properly, so we overwrite the target with solid color */
100         SDL_SetRenderDrawBlendMode(ctx->renderer, SDL_BLENDMODE_NONE);
101         SDL_RenderFillRect(ctx->renderer, NULL);
102         SDL_SetRenderDrawBlendMode(ctx->renderer, SDL_BLENDMODE_BLEND);
103 #if LV_GPU_SDL_CUSTOM_BLEND_MODE
104         internals->mask = lv_draw_sdl_composite_texture_obtain(ctx, LV_DRAW_SDL_COMPOSITE_TEXTURE_ID_STREAM0, w, h,
105                                                                &internals->composition_cached);
106         dump_masks(internals->mask, apply_area);
107 #endif
108     }
109     else if(has_mask) {
110         /* Fallback mask handling. This will at least make bars looks less bad */
111         for(uint8_t i = 0; i < _LV_MASK_MAX_NUM; i++) {
112             _lv_draw_mask_common_dsc_t * comm_param = LV_GC_ROOT(_lv_draw_mask_list[i]).param;
113             if(comm_param == NULL) continue;
114             switch(comm_param->type) {
115                 case LV_DRAW_MASK_TYPE_RADIUS: {
116                         const lv_draw_mask_radius_param_t * param = (const lv_draw_mask_radius_param_t *) comm_param;
117                         if(param->cfg.outer) break;
118                         _lv_area_intersect(clip_out, apply_area, &param->cfg.rect);
119                         break;
120                     }
121                 default:
122                     break;
123             }
124         }
125     }
126     return has_mask;
127 }
128 
lv_draw_sdl_composite_end(lv_draw_sdl_ctx_t * ctx,const lv_area_t * apply_area,lv_blend_mode_t blend_mode)129 void lv_draw_sdl_composite_end(lv_draw_sdl_ctx_t * ctx, const lv_area_t * apply_area, lv_blend_mode_t blend_mode)
130 {
131     lv_draw_sdl_context_internals_t * internals = ctx->internals;
132     SDL_Rect src_rect = {0, 0, lv_area_get_width(apply_area), lv_area_get_height(apply_area)};
133 #if LV_GPU_SDL_CUSTOM_BLEND_MODE
134     if(internals->mask) {
135         SDL_BlendMode mode = SDL_ComposeCustomBlendMode(SDL_BLENDFACTOR_ZERO, SDL_BLENDFACTOR_ONE,
136                                                         SDL_BLENDOPERATION_ADD, SDL_BLENDFACTOR_ZERO,
137                                                         SDL_BLENDFACTOR_SRC_ALPHA, SDL_BLENDOPERATION_ADD);
138         SDL_SetTextureBlendMode(internals->mask, mode);
139         SDL_RenderCopy(ctx->renderer, internals->mask, &src_rect, &src_rect);
140     }
141 #endif
142 
143     /* Shapes are drawn on composite layer when mask or blend mode is present */
144     if(internals->composition) {
145         SDL_Rect dst_rect;
146         lv_area_to_sdl_rect(apply_area, &dst_rect);
147 
148         SDL_SetRenderTarget(ctx->renderer, internals->target_backup);
149         switch(blend_mode) {
150             case LV_BLEND_MODE_NORMAL:
151                 SDL_SetTextureBlendMode(internals->composition, SDL_BLENDMODE_BLEND);
152                 break;
153             case LV_BLEND_MODE_ADDITIVE:
154                 SDL_SetTextureBlendMode(internals->composition, SDL_BLENDMODE_ADD);
155                 break;
156 #if LV_GPU_SDL_CUSTOM_BLEND_MODE
157             case LV_BLEND_MODE_SUBTRACTIVE: {
158                     SDL_BlendMode mode = SDL_ComposeCustomBlendMode(SDL_BLENDFACTOR_ONE, SDL_BLENDFACTOR_ONE,
159                                                                     SDL_BLENDOPERATION_SUBTRACT, SDL_BLENDFACTOR_ONE,
160                                                                     SDL_BLENDFACTOR_ONE, SDL_BLENDOPERATION_SUBTRACT);
161                     SDL_SetTextureBlendMode(internals->composition, mode);
162                     break;
163                 }
164             case LV_BLEND_MODE_MULTIPLY: {
165                     SDL_BlendMode mode = SDL_ComposeCustomBlendMode(SDL_BLENDFACTOR_ZERO, SDL_BLENDFACTOR_SRC_COLOR,
166                                                                     SDL_BLENDOPERATION_ADD, SDL_BLENDFACTOR_ZERO,
167                                                                     SDL_BLENDFACTOR_DST_ALPHA, SDL_BLENDOPERATION_ADD);
168                     SDL_SetTextureBlendMode(internals->composition, mode);
169                     break;
170                 }
171 #endif
172             default:
173                 LV_LOG_WARN("Doesn't support blend mode %d", blend_mode);
174                 SDL_SetTextureBlendMode(internals->composition, SDL_BLENDMODE_BLEND);
175                 /* Unsupported yet */
176                 break;
177         }
178         SDL_RenderCopy(ctx->renderer, internals->composition, &src_rect, &dst_rect);
179         if(!internals->composition_cached) {
180             LV_LOG_WARN("Texture is not cached, this will impact performance.");
181             SDL_DestroyTexture(internals->composition);
182         }
183     }
184 
185     internals->mask = internals->composition = internals->target_backup = NULL;
186 }
187 
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)188 SDL_Texture * lv_draw_sdl_composite_texture_obtain(lv_draw_sdl_ctx_t * ctx, lv_draw_sdl_composite_texture_id_t id,
189                                                    lv_coord_t w, lv_coord_t h, bool * texture_in_cache)
190 {
191     lv_point_t * tex_size = NULL;
192     composite_key_t mask_key = mask_key_create(id);
193     SDL_Texture * result = lv_draw_sdl_texture_cache_get_with_userdata(ctx, &mask_key, sizeof(composite_key_t), NULL,
194                                                                        (void **) &tex_size);
195     if(result == NULL || tex_size->x < w || tex_size->y < h) {
196         lv_coord_t size = next_pow_of_2(LV_MAX(w, h));
197         int access = SDL_TEXTUREACCESS_STREAMING;
198         if(id >= LV_DRAW_SDL_COMPOSITE_TEXTURE_ID_TRANSFORM0) {
199             access = SDL_TEXTUREACCESS_TARGET;
200         }
201         else if(id >= LV_DRAW_SDL_COMPOSITE_TEXTURE_ID_TARGET0) {
202             access = SDL_TEXTUREACCESS_TARGET;
203         }
204         result = SDL_CreateTexture(ctx->renderer, LV_DRAW_SDL_TEXTURE_FORMAT, access, size, size);
205         tex_size = lv_mem_alloc(sizeof(lv_point_t));
206         tex_size->x = tex_size->y = size;
207         bool in_cache = lv_draw_sdl_texture_cache_put_advanced(ctx, &mask_key, sizeof(composite_key_t), result,
208                                                                tex_size, lv_mem_free, 0);
209         if(!in_cache) {
210             lv_mem_free(tex_size);
211         }
212         if(texture_in_cache != NULL) {
213             *texture_in_cache = in_cache;
214         }
215     }
216     else if(texture_in_cache != NULL) {
217         *texture_in_cache = true;
218     }
219 
220     return result;
221 }
222 
223 /**********************
224  *   STATIC FUNCTIONS
225  **********************/
226 
mask_key_create(lv_draw_sdl_composite_texture_id_t type)227 static composite_key_t mask_key_create(lv_draw_sdl_composite_texture_id_t type)
228 {
229     composite_key_t key;
230     /* VERY IMPORTANT! Padding between members is uninitialized, so we have to wipe them manually */
231     SDL_memset(&key, 0, sizeof(key));
232     key.magic = LV_GPU_CACHE_KEY_MAGIC_MASK;
233     key.type = type;
234     return key;
235 }
236 
next_pow_of_2(lv_coord_t num)237 static lv_coord_t next_pow_of_2(lv_coord_t num)
238 {
239     lv_coord_t n = 128;
240     while(n < num && n < 16384) {
241         n = n << 1;
242     }
243     return n;
244 }
245 
dump_masks(SDL_Texture * texture,const lv_area_t * coords)246 static void dump_masks(SDL_Texture * texture, const lv_area_t * coords)
247 {
248     lv_coord_t w = lv_area_get_width(coords), h = lv_area_get_height(coords);
249     SDL_assert(w > 0 && h > 0);
250     SDL_Rect rect = {0, 0, w, h};
251     uint8_t * pixels;
252     int pitch;
253     if(SDL_LockTexture(texture, &rect, (void **) &pixels, &pitch) != 0) return;
254 
255     lv_opa_t * line_buf = lv_mem_buf_get(rect.w);
256     for(lv_coord_t y = 0; y < rect.h; y++) {
257         lv_memset_ff(line_buf, rect.w);
258         lv_coord_t abs_x = (lv_coord_t) coords->x1, abs_y = (lv_coord_t)(y + coords->y1), len = (lv_coord_t) rect.w;
259         lv_draw_mask_res_t res;
260         res = lv_draw_mask_apply(line_buf, abs_x, abs_y, len);
261         if(res == LV_DRAW_MASK_RES_TRANSP) {
262             lv_memset_00(&pixels[y * pitch], 4 * rect.w);
263         }
264         else if(res == LV_DRAW_MASK_RES_FULL_COVER) {
265             lv_memset_ff(&pixels[y * pitch], 4 * rect.w);
266         }
267         else {
268             for(int x = 0; x < rect.w; x++) {
269                 const size_t idx = y * pitch + x * 4;
270                 pixels[idx] = line_buf[x];
271                 pixels[idx + 1] = pixels[idx + 2] = pixels[idx + 3] = 0xFF;
272             }
273         }
274     }
275     lv_mem_buf_release(line_buf);
276     SDL_UnlockTexture(texture);
277 }
278 
279 #endif /*LV_USE_GPU_SDL*/
280