LVGL源碼學習之渲染、更新過程(2)---無效區域的處理

LVGL版本:8.1

往期回顧:

LVGL源碼學習之渲染、更新過程(1)---標記和激活

區域合并

? ? ? ?在前面的代碼分析中,發現標記無效區域的工作其實很繁瑣,雖然大部分區域因為包含關系被剔除,但仍可能存在相互交叉的區域,導致重復計算減少效率。因此在正式處理前,還需要根據情況選擇性地合并這些交叉的區域。

? ? ? ?前面在標記無效區域時,將區域坐標及其數量都存儲在inv_areas[]數組和inv_p這兩個屬性里,而還有一個屬性inv_area_joined[]用于標明被合并掉的區域(數組元素值為1表示該索引對應的無效區域已被合并,后續刷新將略過它):

//lv_hal_disp.h
typedef struct _lv_disp_t {/*......*//** Invalidated (marked to redraw) areas*/lv_area_t inv_areas[LV_INV_BUF_SIZE];  //數組,存儲前面標記的所有無效區域坐標uint16_t inv_p;  //無效區域數量uint8_t inv_area_joined[LV_INV_BUF_SIZE];  //數組,用于標明被合并的區域/*......*/
} lv_disp_t;

? ? ? ?具體合并算法過程如下面函數,其中用join_in下標表示合并主體區域,用join_from下標表示被合并區域:

//lv_refr.c
static void lv_refr_join_area(void)
{uint32_t join_from;uint32_t join_in;lv_area_t joined_area;/* 外循環,每次取一個合并主體區域 */for(join_in = 0; join_in < disp_refr->inv_p; join_in++) {if(disp_refr->inv_area_joined[join_in] != 0) continue;  //如果該區域已經被合并,則跳過/* 內循環,將其它被合并區域和外循環的合并主體區域進行比對,查看重疊情況 */for(join_from = 0; join_from < disp_refr->inv_p; join_from++) {if(disp_refr->inv_area_joined[join_from] != 0 || join_in == join_from) {continue;     //如果該區域是合并主體自身,或者該區域已經被合并,則跳過}/* 檢查兩塊區域是否存在重疊 */if(_lv_area_is_on(&disp_refr->inv_areas[join_in], &disp_refr->inv_areas[join_from]) == false) {continue;}/* 生成一塊更大的區域,使它剛好同時容納兩塊重疊的區域 */_lv_area_join(&joined_area, &disp_refr->inv_areas[join_in], &disp_refr->inv_areas[join_from]);/* 只有當合成區域面積小于兩塊重疊區域面積之和,才將生成區域保存 */if(lv_area_get_size(&joined_area) < (lv_area_get_size(&disp_refr->inv_areas[join_in]) +lv_area_get_size(&disp_refr->inv_areas[join_from]))) {lv_area_copy(&disp_refr->inv_areas[join_in], &joined_area);/* 標記被合并的區域,下次遍歷將跳過這塊區域 */disp_refr->inv_area_joined[join_from] = 1;}}}
}

? ? ? ?其中的_lv_area_is_on()函數就是檢查兩塊區域是否存在重疊:

? ? ? ?如果有重疊,_lv_area_join()函數會將兩塊重疊區域合成一個更大的區域。(或者這樣描述:生成一個新的區域,使它剛好同時容納兩塊重疊區域)。

? ? ? ?合成新區域后,還需要對比和之前兩塊重疊區域的面積進行比較,若小于兩塊面積之和,則將新區域覆蓋到inv_area[join_in],并將inv_area_joined[join_from]置1,表示被合并(后續會被忽略),否則廢棄。

? ? ? ?以上過程可以用下圖來形象解釋:

區域分塊更新

? ? ? ?在注冊顯示器時,用于顯示的緩存大小一般會設置為顯示器像素總數,但有時候內存不足,可能僅設置了一個小緩存,同時無效區域的大小是有可能超出這個緩存大小的,因此在合并好區域后,又調用lv_refr_areas()對每個區域按行再進行一次分塊刷新,具體分塊原理后面會進一步說明。

//lv_refr.c
static void lv_refr_areas(void)
{px_num = 0;if(disp_refr->inv_p == 0) return;  //當前不存在無效區域,直接返回/* 找到最后一塊要繪制的區域 */int32_t i;int32_t last_i = 0;for(i = disp_refr->inv_p - 1; i >= 0; i--) {if(disp_refr->inv_area_joined[i] == 0) {last_i = i;break;}}disp_refr->driver->draw_buf->last_area = 0;disp_refr->driver->draw_buf->last_part = 0;for(i = 0; i < disp_refr->inv_p; i++) {/* 更新合并后的區域 */if(disp_refr->inv_area_joined[i] == 0) {/* 最后一塊合并區域,置位last_area標志 */if(i == last_i) disp_refr->driver->draw_buf->last_area = 1;disp_refr->driver->draw_buf->last_part = 0;  //在開始處理前清空last_part標志,該標志會在下面的函數完成后置位lv_refr_area(&disp_refr->inv_areas[i]);  //具體進行分塊刷新的函數px_num += lv_area_get_size(&disp_refr->inv_areas[i]);  //累計區域像素數}}
}

? ? ? ? 這里涉及到兩個標志位,后續的解讀會用“區域”來指代area,用“”來指代part

//lv_hal_disp.h
typedef struct _lv_disp_draw_buf_t {/*......*/volatile uint32_t last_area : 1;  /* 1表示最后一塊區域正在被渲染*/volatile uint32_t last_part : 1;  /* 1表示該區域的最后一塊正在被渲染*//*......*/
} lv_disp_draw_buf_t;

? ? ? ?上面函數針對每個合并后的區域,都使用lv_refr_area()進行分塊刷新,具體分塊的方法可以通俗解釋為“在區域內等間距地畫幾條貫穿的橫線分隔出幾塊”,即對區域的行數進行拆分,注意每次分塊大小不能大于計算緩存(注冊時傳入,通常等于顯示器總像素數),下面用圖形解析不同的情況(綠色框代表顯示器像素范圍,紅色框代表無效區域):

①渲染區域寬度超出顯示器寬度(這通常不會發生,但為了減少可能存在的錯誤,依然考慮在內),此時單次刷新的最大高度被限制為max_row=buffer_size/area_width,根據區域高度大小有三種情況:

  • 區域高度很小

  • 區域高度很大但未超出顯示器像素高度

  • 區域高度完全超出顯示器像素高度

? ? ? ?注意上面第二、三點,最后一次刷新的分塊高度,通常會小于理論的最大高度(max_row

②渲染區域寬高在顯示器內,但顯示緩存不足(小于顯示器像素總數),也會發生和①類似的情況:

③渲染區域的寬度在顯示器寬度內,但高度超出顯示器高度(這通常也不會發生),此時截取掉超出的部分,僅渲染處于顯示器內的區域,并一次性刷新完畢:

④渲染區域的寬高都在顯示器范圍內,也是一次性刷新:

? ? ? ? 代碼如下:

//lv_refr.c
static void lv_refr_area(const lv_area_t * area_p)
{/* 如果設置了全刷新,直接刷新全屏范圍,并設置last_part為1表示最后一塊 */if(disp_refr->driver->full_refresh) {lv_disp_draw_buf_t * draw_buf = lv_disp_get_draw_buf(disp_refr);draw_buf->area.x1        = 0;draw_buf->area.x2        = lv_disp_get_hor_res(disp_refr) - 1;draw_buf->area.y1        = 0;draw_buf->area.y2        = lv_disp_get_ver_res(disp_refr) - 1;disp_refr->driver->draw_buf->last_part = 1;lv_refr_area_part(area_p);return;}/* 常規刷新方式: 分塊刷新區域(重點) */lv_disp_draw_buf_t * draw_buf = lv_disp_get_draw_buf(disp_refr);  //獲取顯示緩存buffer/* 計算單次刷新的最大行數 */lv_coord_t w = lv_area_get_width(area_p);   //待刷新區域的寬度lv_coord_t h = lv_area_get_height(area_p);   //待刷新區域的高度lv_coord_t y2 = area_p->y2 >= lv_disp_get_ver_res(disp_refr) ?lv_disp_get_ver_res(disp_refr) - 1 : area_p->y2;  //下界不能超出屏幕int32_t max_row = (uint32_t)draw_buf->size / w;  //size就是顯示器的總像素數if(max_row > h) max_row = h;  //max_row就是分塊后,單次刷新的最大行數/* 舍入操作(如有定義) */if(disp_refr->driver->rounder_cb) {lv_area_t tmp;tmp.x1 = 0;tmp.x2 = 0;tmp.y1 = 0;lv_coord_t h_tmp = max_row;do {tmp.y2 = h_tmp - 1;disp_refr->driver->rounder_cb(disp_refr->driver, &tmp);/* 如果舍入結果小于max_row,則符合條件,跳出 */if(lv_area_get_height(&tmp) <= max_row) break;/* 減少分塊高度,以匹配顯示器的舍入規則 */h_tmp--;} while(h_tmp > 0);if(h_tmp <= 0) {LV_LOG_WARN("Can't set draw_buf height using the round function. (Wrong round_cb or to ""small draw_buf)");return;}else {max_row = tmp.y2 + 1;}}/* 在direct模式下,所有緩存將直接繪制到絕對坐標位置上 */if(disp_refr->driver->direct_mode) {draw_buf->area.x1 = 0;draw_buf->area.x2 = lv_disp_get_hor_res(disp_refr) - 1;draw_buf->area.y1 = 0;draw_buf->area.y2 = lv_disp_get_ver_res(disp_refr) - 1;disp_refr->driver->draw_buf->last_part = disp_refr->driver->draw_buf->last_area;lv_refr_area_part(area_p);}else {/* 常規刷新模式下,從給定區域的起始位置開始分塊刷新 */lv_coord_t row;lv_coord_t row_last = 0;for(row = area_p->y1; row + max_row - 1 <= y2; row += max_row) {/* 計算下一塊的起始和結束行數 */draw_buf->area.x1 = area_p->x1;draw_buf->area.x2 = area_p->x2;draw_buf->area.y1 = row;draw_buf->area.y2 = row + max_row - 1;if(draw_buf->area.y2 > y2) draw_buf->area.y2 = y2;row_last = draw_buf->area.y2;if(y2 == row_last) disp_refr->driver->draw_buf->last_part = 1;  //y2正好時最后一行lv_refr_area_part(area_p);}/* y2是行下限,上面的均勻分塊可能會導致最后一次遍歷錯過一些行,在這里要補上 */if(y2 != row_last) {draw_buf->area.x1 = area_p->x1;draw_buf->area.x2 = area_p->x2;draw_buf->area.y1 = row;draw_buf->area.y2 = y2;  //將y2作為底部disp_refr->driver->draw_buf->last_part = 1;lv_refr_area_part(area_p);}}
}

? ? ? ?在每次分塊完成后,都是使用lv_refr_area_part()函數進一步處理,該函數里需要先找到能完全覆蓋該無效區域的最上層對象(子對象總是默認放置在父對象的上層),

//lv_refr.c
static void lv_refr_area_part(const lv_area_t * area_p)
{lv_disp_draw_buf_t * draw_buf = lv_disp_get_draw_buf(disp_refr);/* 單一緩存下,當緩存正在刷寫到屏幕,需要等待直至緩存釋放 */if(draw_buf->buf1 && !draw_buf->buf2) {while(draw_buf->flushing) {  //flushing標志置位表示正在刷寫,需要調用lv_disp_flush_ready()進行清除if(disp_refr->driver->wait_cb) disp_refr->driver->wait_cb(disp_refr->driver);}}lv_obj_t * top_act_scr = NULL;lv_obj_t * top_prev_scr = NULL;/* 取無效區域和活動屏幕的交集 */lv_area_t start_mask;_lv_area_intersect(&start_mask, area_p, &draw_buf->area);/* 獲取活躍屏幕內,能完全覆蓋無效區域的最上層對象(后創建的在上層) */top_act_scr = lv_refr_get_top_obj(&start_mask, lv_disp_get_scr_act(disp_refr));if(disp_refr->prev_scr) {  //尋找上一幀是否存在上述類型的對象top_prev_scr = lv_refr_get_top_obj(&start_mask, disp_refr->prev_scr);}/* 如果不存在能完全覆蓋該區域的對象,則先繪制背景 */if(top_act_scr == NULL && top_prev_scr == NULL) {if(disp_refr->bg_fn) {disp_refr->bg_fn(&start_mask);} else if(disp_refr->bg_img) {lv_draw_img_dsc_t dsc;lv_draw_img_dsc_init(&dsc);dsc.opa = disp_refr->bg_opa;lv_img_header_t header;lv_res_t res;res = lv_img_decoder_get_info(disp_refr->bg_img, &header);if(res == LV_RES_OK) {lv_area_t a;lv_area_set(&a, 0, 0, header.w - 1, header.h - 1);lv_draw_img(&a, &start_mask, disp_refr->bg_img, &dsc);}else {LV_LOG_WARN("Can't draw the background image");}}else {lv_draw_rect_dsc_t dsc;lv_draw_rect_dsc_init(&dsc);dsc.bg_color = disp_refr->bg_color;dsc.bg_opa = disp_refr->bg_opa;lv_draw_rect(&start_mask, &start_mask, &dsc);}}/* 刷新上一幀屏幕,僅動畫使用 */if(disp_refr->prev_scr) {/* 獲取上一幀該區域未被遮擋的上層對象 */if(top_prev_scr == NULL) {top_prev_scr = disp_refr->prev_scr;}/* 對該對象進行刷新 */lv_refr_obj_and_children(top_prev_scr, &start_mask);}if(top_act_scr == NULL) {top_act_scr = disp_refr->act_scr;}/* 刷新活躍屏幕(act_scr)上對應的對象 */lv_refr_obj_and_children(top_act_scr, &start_mask);/* 無條件地刷新頂層和系統層屏幕 */lv_refr_obj_and_children(lv_disp_get_layer_top(disp_refr), &start_mask);lv_refr_obj_and_children(lv_disp_get_layer_sys(disp_refr), &start_mask);/* 在雙緩沖模式下,僅在所有區域重新渲染完畢后,進行一次繪制(flush)* 普通模式下,每個區域的更新都會進行一次繪制 */if(disp_refr->driver->full_refresh == false) {draw_buf_flush();}
}

? ? ? ?層層解剖,最后使用lv_refr_obj_and_children()函數進行更新。該函數除了要刷新上面說的完全覆蓋區域的上層對象,還要更新其“弟弟”節點,因為這些“弟弟”們也可以覆蓋在該對象上,進一步的,還要更新該對象的父對象的“弟弟”節點,并一直向上尋找直至頂層屏幕結束。如下圖,粉色節點為上層對象,橙色節點為同樣需要更新的“弟弟”或“叔叔”節點,綠色節點為這些待更新節點的子節點(會被一同刷新)。

//lv_refr.c
static void lv_refr_obj_and_children(lv_obj_t * top_p, const lv_area_t * mask_p)
{/*Normally always will be a top_obj (at least the screen)*but in special cases (e.g. if the screen has alpha) it won't.*In this case use the screen directly*/if(top_p == NULL) top_p = lv_disp_get_scr_act(disp_refr);if(top_p == NULL) return;  /*Shouldn't happen*//* 刷新該對象及其子對象 */lv_refr_obj(top_p, mask_p);/* 接下來的操作都是刷新該節點的“弟弟”節點(后創建但同屬一個父節點) */lv_obj_t * par;lv_obj_t * border_p = top_p;par = lv_obj_get_parent(top_p);/* 尋找“弟弟”節點 */while(par != NULL) {bool go = false;uint32_t i;uint32_t child_cnt = lv_obj_get_child_cnt(par);for(i = 0; i < child_cnt; i++) {lv_obj_t * child = par->spec_attr->children[i];if(!go) {if(child == border_p) go = true;  //標記自身,之后遍歷的都是“弟弟”了}else {lv_refr_obj(child, mask_p);}}/* 調用父節點的“后期繪制事件”(DRAW_POST)相關回調函數 */lv_event_send(par, LV_EVENT_DRAW_POST_BEGIN, (void *)mask_p);lv_event_send(par, LV_EVENT_DRAW_POST, (void *)mask_p);lv_event_send(par, LV_EVENT_DRAW_POST_END, (void *)mask_p);/* 對父節點同樣執行一遍上述操作,直至沒有下一個父節點(頂層屏幕) */border_p = par;par = lv_obj_get_parent(par);}
}

? ? ? ?上面的迭代,每次遍歷一個對象,都使用lv_refr_obj()函數來更新其自身及其所有子孫節點。該函數就是更新區域的最后一步了,用于更新傳入的節點,并對所有子孫節點做同樣的遞歸更新操作。

//lv_refr.c
static void lv_refr_obj(lv_obj_t * obj, const lv_area_t * mask_ori_p)
{/* 不刷新隱藏的節點對象 */if(lv_obj_has_flag(obj, LV_OBJ_FLAG_HIDDEN)) return;bool union_ok; /* 傳入對象的坐標區域和無效區域的子集 */lv_area_t obj_mask;lv_area_t obj_ext_mask;lv_area_t obj_area;lv_coord_t ext_size = _lv_obj_get_ext_draw_size(obj);  //獲取繪制階段的額外范圍lv_obj_get_coords(obj, &obj_area);  //獲取對象坐標范圍obj_area.x1 -= ext_size;obj_area.y1 -= ext_size;obj_area.x2 += ext_size;obj_area.y2 += ext_size;union_ok = _lv_area_intersect(&obj_ext_mask, mask_ori_p, &obj_area);/* 僅當對象主繪制區域和傳入的無效區域存在交集時才進行繪制 */if(union_ok != false) {/* 主繪制階段 */lv_event_send(obj, LV_EVENT_DRAW_MAIN_BEGIN, &obj_ext_mask);lv_event_send(obj, LV_EVENT_DRAW_MAIN, &obj_ext_mask);lv_event_send(obj, LV_EVENT_DRAW_MAIN_END, &obj_ext_mask);/* 去掉主繪制階段的額外范圍,進行子節點繪制(子節點無法看到這些范圍) */lv_obj_get_coords(obj, &obj_area);union_ok = _lv_area_intersect(&obj_mask, mask_ori_p, &obj_area);if(union_ok != false) {lv_area_t mask_child; /*Mask from obj and its child*/lv_area_t child_area;uint32_t i;uint32_t child_cnt = lv_obj_get_child_cnt(obj);for(i = 0; i < child_cnt; i++) {lv_obj_t * child = obj->spec_attr->children[i];lv_obj_get_coords(child, &child_area);ext_size = _lv_obj_get_ext_draw_size(child);child_area.x1 -= ext_size;child_area.y1 -= ext_size;child_area.x2 += ext_size;child_area.y2 += ext_size;/* 獲取子節點和無效區域的交集 */union_ok = _lv_area_intersect(&mask_child, &obj_mask, &child_area);/* 如果有交集則遞歸調用該函數刷新子節點 */if(union_ok) {/* 刷新下一個子節點 */lv_refr_obj(child, &mask_child);}}}/* 當所有子節點都繪制完畢,進入“后期繪制”階段 */lv_event_send(obj, LV_EVENT_DRAW_POST_BEGIN, &obj_ext_mask);lv_event_send(obj, LV_EVENT_DRAW_POST, &obj_ext_mask);lv_event_send(obj, LV_EVENT_DRAW_POST_END, &obj_ext_mask);}
}

? ? ? ?到這里才發現,最終遍歷到具體的對象和區域后,會發送DRAW相關事件給該對象,而更新的關鍵就在于DRAW事件的相關回調函數。

? ? ? ?到這里回過頭捋一下更新任務內部實現的調用路徑:

-->?_lv_disp_refr_timer(tmr),更新任務主體

? ? ?-->?lv_obj_update_layout(screen),布局更新,將臟數據進一步轉化成無效區域

? ? ?--> lv_refr_join_area(),合并重疊的無效區域

? ? ? --> lv_refr_areas(),遍歷無效區域進行更新

? ? ? ? ? ? -->?lv_refr_area(area),將每一片無效區域進一步分塊

? ? ? ? ? ? ? ? ? -->?lv_refr_part(area),找到分塊后,完全覆蓋該塊的最頂層元素進行遍歷

? ? ? ? ? ? ? ? ? ? ? ?-->?lv_refr_obj_and_children(top_obj, area),遍歷最頂層元素及其“弟弟”節點們

? ? ? ? ? ? ? ? ? ? ? ? ? ? --> lv_refr_obj(top_obj, area),向需要更新的對象發送draw相關事件

? ? ?--> draw_buf_flush(),將緩存刷寫到顯示器

? ? ? ?下一片文章,將分析draw事件回調函數如何重繪對象并最終刷寫緩沖。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/80102.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/80102.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/80102.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

01 dnsmasq 中 dns服務

前言 這里我們主要是 來看一下 dns 服務器這邊的相關業務處理 通常來說 在我們日常生活中 還是經常會需要使用 dns 的情況, 主要是更加友好的去給一個主機命名一個別名 比如 現在我的應用在服務器 192.168.220.133 但是我不想記這個生硬的 ip, 我可能更期望記錄一個域名, …

最優化方法Python計算:有約束優化應用——線性Lasso回歸分類器

利用線性Lasso模型類LineLassoModel類&#xff08;見博文《最優化方法Python計算&#xff1a;有約束優化應用——線性Lasso回歸預測器》&#xff09;及分類器類Classification&#xff08;見博文《最優化方法Python計算&#xff1a;無約束優化應用——線性回歸分類器》&#xf…

Python基礎學習-Day20

目錄 奇異值分解&#xff08;SVD&#xff09;的輸入和輸出奇異值的應用實際案例1. 問題分析2. 解決方案&#xff1a;對測試集應用相同的變換3. 為什么不能對測試集單獨做 SVD&#xff1f;4. 代碼示例&#xff1a;訓練集和測試集的 SVD 降維6. 實際操作中的注意事項 奇異值分解&…

2025年 全新 AI 編程工具 Cursor 安裝使用教程

一、Cursor 軟件下載 首選&#xff0c;登錄Cursor官網&#xff0c;進行軟件下載&#xff0c;官網下載地址如下&#xff1a; Cursor AI IDE 下載 二、Cursor軟件安裝配置 此處以Windows10系統安裝為例&#xff0c;下載完成之后&#xff0c;右鍵安裝包&#xff0c;以管理員身份…

[vue]error:0308010C:digital envelope routines::unsupported

npm run dev 報錯&#xff1a; \node_modules\webpack\hot\dev-server.jsnode:internal/crypto/hash:71 this[kHandle] new _Hash(algorithm, xofLen); Error: error:0308010C:digital envelope routines::unsupported opensslErrorStack: [ error:03000086:digital env…

開放的力量:新零售生態的共贏密碼

當某頭部生鮮平臺向供應商開放銷售預測系統后&#xff0c;合作伙伴的庫存周轉率竟提升12%——這個反常識的案例&#xff0c;正在重塑商業競爭的底層邏輯。 生態共建三板斧 ▌模塊化設計&#xff1a;像搭積木一樣開放 ? 樂高式API架構&#xff1a;30%接口支持自由組合&#xff…

深入理解Spring緩存注解:@Cacheable與@CacheEvict

在現代應用程序開發中&#xff0c;緩存是提升系統性能的重要手段。Spring框架提供了一套簡潔而強大的緩存抽象&#xff0c;其中Cacheable和CacheEvict是兩個最常用的注解。本文將深入探討這兩個注解的工作原理、使用場景以及最佳實踐。 1. Cacheable注解 基本概念 Cacheable…

[python] 函數3-python內置函數

一 內置函數 導入:import builtins 1.1 查看內置函數 大寫字母開頭的一般是內置變量小寫的一般是內置函數 import builtins print(dir(builtins)) 1.2 abs() 求絕對值 print(abs(-10)) 1.3 sum()求和 不能直接用純數字,因為不是可迭代對象 運算時只要一個是浮點數,結果就…

QT異步線程通信

在使用 QThreadPool 提交任務后&#xff0c;如果你需要知道任務何時完成&#xff0c;并且需要使用任務的執行結果&#xff0c;可以通過以下幾種方式來實現&#xff1a; 1. 使用信號和槽 QRunnable 提供了一個 finished() 信號&#xff0c;當任務執行完成后會發出。你可以在任…

利用并行處理提高LabVIEW程序執行速度

在 LabVIEW 編程中&#xff0c;提升程序執行速度是優化系統性能的關鍵&#xff0c;而并行處理技術則是實現這一目標的有力武器。通過合理運用并行處理&#xff0c;不僅能加快程序運行&#xff0c;還能增強系統的穩定性和響應能力。下面將結合實際案例&#xff0c;深入探討如何利…

機器學習第三講:監督學習 → 帶答案的學習冊,如預測房價時需要歷史價格數據

機器學習第三講&#xff1a;監督學習 → 帶答案的學習冊&#xff0c;如預測房價時需要歷史價格數據 資料取自《零基礎學機器學習》。 查看總目錄&#xff1a;學習大綱 關于DeepSeek本地部署指南可以看下我之前寫的文章&#xff1a;DeepSeek R1本地與線上滿血版部署&#xff1…

Open CASCADE學習|實現裁剪操作

1. 引言 Open CASCADE (簡稱OCC) 是一個功能強大的開源幾何建模內核&#xff0c;廣泛應用于CAD/CAM/CAE領域。裁剪操作作為幾何建模中的基礎功能&#xff0c;在模型編輯、布爾運算、幾何分析等方面有著重要作用。本文將全面探討Open CASCADE中的裁剪操作實現原理、應用場景及具…

【redis】分片方案

Redis分片&#xff08;Sharding&#xff09;是解決單機性能瓶頸的核心技術&#xff0c;其本質是將數據分散存儲到多個Redis節點&#xff08;實例&#xff09;中&#xff0c;每個實例將只是所有鍵的一個子集&#xff0c;通過水平擴展提升系統容量和性能。 分片的核心價值 性能提…

RGB矩陣照明系統詳解及WS2812配置指南

RGB矩陣照明系統詳解及WS2812配置指南 一、RGB矩陣照明簡介 RGB矩陣照明是一種強大的功能&#xff0c;允許使用外部驅動器驅動的RGB LED矩陣為鍵盤增添絢麗的燈光效果。該系統與RGBLIGHT功能無縫集成&#xff0c;因此您可以使用與RGBLIGHT相同的鍵碼來控制它&#xff0c;操作…

[250509] x-cmd 發布 v0.5.11 beta:x ping 優化、AI 模型新增支持和語言變量調整

目錄 X-CMD 發布 v0.5.11 beta&#x1f4c3;Changelog&#x1f9e9; ping&#x1f9e9; openai&#x1f9e9; gemini&#x1f9e9; asdf&#x1f9e9; mac? 升級指南 X-CMD 發布 v0.5.11 beta &#x1f4c3;Changelog &#x1f9e9; ping 調整 x ping 默認參數為 bing.com&a…

嵌入式開發學習日志Day17

第十一章 結構體與共用體 一、結構體 1、結構體 一般形式 【struct 標識符】 結構體中的標識符一般首字母大寫&#xff1b; 【.】結構體成員運算符&#xff1b; 優先級 1 級 結合方向&#xff1a;從左至右&#xff1b; 【->】:指向結構體成員運算符&#x…

發那科機器人5(異常事件和程序備份加載+ROBOGUIDE離線仿真)

發那科機器人5(異常事件和程序備份加載+ROBOGUIDE離線仿真) 一,異常事件和程序備份加載1,常見異常事件2,零點復歸介紹3,程序備份-加載(未整理)二,`ROBOGUIDE`離線仿真1,仿真軟件簡介及安裝步驟(未整理)2,機器人==導入與工具==與==工件添加==2.1,機器人導入(未整…

青少年編程與數學 02-019 Rust 編程基礎 01課題、環境準備

青少年編程與數學 02-019 Rust 編程基礎 01課題、環境準備 一、Rust核心特性應用場景開發工具社區與生態 二、Rust 和 Python 比較1. **內存安全與并發編程**2. **性能**3. **零成本抽象**4. **跨平臺支持**5. **社區與生態系統**6. **錯誤處理**7. **安全性**適用場景總結 三、…

Java反射 八股版

目錄 一、核心概念闡釋 1. Class類 2. Constructor類 3. Method類 4. Field類 二、典型應用場景 1. 框架開發 2. 單元測試 3. JSON序列化/反序列化 三、性能考量 四、安全與訪問控制 1. 安全管理器限制 2. 打破封裝性 3. 安全風險 五、版本兼容性問題 六、最佳…

操作系統的初步了解

目錄 引言&#xff1a;什么是操作系統&#xff1f; 一、設計操作系統的目的 二、操作系統是做什么的&#xff1a; 操作系統主要有四大核心任務&#xff1a; 1. 管理硬件 2. 運行軟件 3. 存儲數據 4. 提供用戶界面 如何理解操作系統的管理呢&#xff1f; 1. 什么是操作…