Blender 之 Splash 代碼分析



  
  注:以下內容基於 Blender 2.7x 版本工程,其它低版本可能有改動。

  Blender啟動完成時,會出現一個畫面,英文叫Splash。默認是打開的,可以在設置里關閉。在文件菜單里點擊用戶首選項(快捷鍵Ctrl + Alt + U),在彈出的窗口第一個Tab頁面,也就是界面(Interface)的右下角,有一個選項 Show Splash,默認打了勾,關閉然后最下一行的 Save User Settings。這樣,下次啟動的時候就不會出現Splash。

  假設你已經下載好了blender工程代碼,下面通過Splash講解C語言Operator的實現,前一篇是關於Python的Operator。

 

datatoc

  Splash資源所在的文件夾是 blender/release/datafiles/ ,一起的還有各種圖標、字體、SVG矢量圖、畫筆以及matcap效果圖。
  source/blender/editors/datafiles/CMakeLists.txt 會調用 data_to_c_simple 函數從圖片格式轉換成C代碼(其實就是長度加二進制數據),blender 需要在源代碼路徑外編譯(out of source build) ,對應生成在 blender-build/release/datafiles/ 。
data_to_c_simple(../../../../release/datafiles/splash.png SRC)
  data_to_c_simple 函數(cmake的宏)定義在 build_files/cmake/macros.cmake
  data_to_c 需要提供file_from、file_to參數,data_to_c_simple 則僅提供 file_from 參數,file_to 的值為 ${file_from}.c。
  調用的命令是 datatoc 其工程在 source/blender/datatoc/ 。

  該 CMakeLists.txt 的最后一句指明生成 bf_editor_datafiles.lib
blender_add_lib(bf_editor_datafiles "${SRC}" "${INC}" "${INC_SYS}")

bf_editor_datafiles.vcxproj -> blender-build/lib/Debug/bf_editor_datafiles.lib


  blender/source/blenderplayer/CMakeLists.txt
  bf_editor_datafiles 被包含在 BLENDER_SORTED_LIBS 里,blender.exe blenderplayer.exe 會依賴這些工程庫。
target_link_libraries(blenderplayer ${BLENDER_SORTED_LIBS})
target_link_libraries(blender ${BLENDER_SORTED_LIBS})

  這些圖片資源最終轉換成C數組數據鏈接到.exe文件里。
好處是:如果缺少相關資源(寫錯名字、用了中文字符、或者圖片格式誤寫),編譯期就會報錯;如果僅僅是替換掉一張圖片的話,錯誤只能在運行時被發現,導致出錯。
壞處是:不僅僅是換掉圖片資源就好了的,還需要重新編譯。這讓一些想把splash換成自己作品的藝術家望而卻步。
后話:Google Summer Of Code 2016 列出了可配置的Splash想法(Configuration Splash Screen)。

 

main函數

  Blender.sln 解決方案包含多個工程,cmake工具會額外生成 ALL_BUILD / INSTALL / PACKAGE / RUN_TESTS / ZERO_CHECK 等幾個工程。其中,ALL_BUILD與INSTALL相當於makefile里面的make與install命令。
  編譯完成,在運行的時候,需要將啟動工程從 ALL_BUILD 修改成 blender,否則會提示 Unable to start program 'build\x64\Debug\ALL_BUILD' 拒絕訪問。(在blender工程上鼠標右鍵后,選擇 Set as StartUp Project 即可)

  一切的一切,要從main函數開始……
  main函數在 blender 工程里的 source/creator/creator.c ,初始化子系統(圖像、修改器、畫筆、Python、節點、材質等)、處理命令行參數、進入消息循環WM_main()或者退出循環在后台運行(-b參數可以指定在后台渲染)。
BLI_argsAdd(ba, 1, "-b", "--background", "\n\tRun in background (often used for UI-less rendering)", background_mode, NULL);

  所有有UI的應用程序會有消息循環機制。Blender 的是在 WM_main(C);
  WM_main 的實現在 source/blender/windowmanager/intern/wm.c ,很簡單的幾行代碼。

void WM_main(bContext *C)
{
    while (1) {
        
        /* get events from ghost, handle window events, add to window queues */
        wm_window_process_events(C); 
        
        /* per window, all events to the window, screen, area and region handlers */
        wm_event_do_handlers(C);
        
        /* events have left notes about changes, we handle and cache it */
        wm_event_do_notifiers(C);
        
        /* execute cached changes draw */
        wm_draw_update(C);
    }
}

在 WM_main(C); 一行的上面是關於splash的,

if(!G.file_loaded)
    WM_init_splash(C);

  Blender 有兩個重要的數據結構 Global G; 和 UserDef U; ,見名知意。
  Global 的字段 file_loaded 是 bool 類型的,但是字符串全局搜索到的是賦值為整形1,其實就是true。
source/blender/windowmanager/intern/wm_operators.c: G.file_loaded = 1; /* prevents splash to show */
  file_loaded 為 true 則阻止加載splash,否則啟動時加載splash。

 

Splash

  關於 splash 的代碼,就在 WM_init_splash(C); 這一行了。
  WM是WindowManager的縮寫,C語言缺少 C++ 的 namespace 概念,這樣附帶模塊命名以防沖突。
  來看看代碼中有關splash的地方,Blender一個很常見的概念是Operator。
source/blender/windowmanager/intern/wm_init_exit.c

void WM_init_splash(bContext *C)
{
    if ((U.uiflag & USER_SPLASH_DISABLE) == 0) {
        wmWindowManager *wm = CTX_wm_manager(C);
        wmWindow *prevwin = CTX_wm_window(C);
    
        if (wm->windows.first) {
            CTX_wm_window_set(C, wm->windows.first);
            WM_operator_name_call(C, "WM_OT_splash", WM_OP_INVOKE_DEFAULT, NULL);
            CTX_wm_window_set(C, prevwin);
        }
    }
}

  U.uiflag的比特位差不多用光了,又開了uiflag2。其中,USER_SPLASH_DISABLE 對應上面用戶偏好里的 Show Splash。
  bContext 是很重要的數據結構,通過指針傳遞,函數定義在 source/blender/blenkernel/intern/context.c,很多地方都引用了它,應該定義在 .h 文件里的。

  給變量取好名字,更方便讀代碼。WM_operator_name_call 見名知意,根據Operator的名字來調用函數,比如上面就是想調用 "WM_OT_splash" 函數。

int WM_operator_name_call(bContext *C, const char *opstring, short context, PointerRNA *properties)
{
    wmOperatorType *ot = WM_operatortype_find(opstring, 0);
    if (ot) {
        return WM_operator_name_call_ptr(C, ot, context, properties);
    }

    return 0;
}

  在 blender/source/blender/windowmanager/intern/wm_operators.c 你可以看到關於 Operator 的各種操作,通過函數 WM_operatortype_append 可以添加新的 Operator,由於有很多 Operator,Blender 用自己的哈希表 GHash *global_ops_hash 管理,WM_OT_splash 是在 WM_init() 初始化時添加的。

/* called on initialize WM_init() */
void wm_operatortype_init(void)
{
    /* reserve size is set based on blender default setup */
    global_ops_hash = BLI_ghash_str_new_ex("wm_operatortype_init gh", 2048);
    
    ...
    WM_operatortype_append(WM_OT_splash);
    ...
}

調用的棧是這樣的
int main(int argc, const char **argv)
  WM_init(C, argc, (const char **)argv);
    wm_operatortype_init();
      WM_operatortype_append(WM_OT_splash);

  WM_OT_splash,WM是Operator的種類,_OT_ 是 Operator Type,構成Operator ID名稱的一部分,該函數填充 wmOperatorType 里的字段。

static void WM_OT_splash(wmOperatorType *ot)
{
    ot->name = "Splash Screen";
    ot->idname = "WM_OT_splash";
    ot->description = "Open the splash screen with release info";
    
    ot->invoke = wm_splash_invoke;
    ot->poll = WM_operator_winactive;
}

 

  前一篇講到過 name、idname、description。struct wmOperatorType 也給了注釋。name 會在UI上顯示出來的。idname 名字應該和函數名字一致,這里可以用 __func__ 宏的。description 是提示信息,鼠標放在上面會顯示出來。
  poll回調函數用來測試該Operator是否可以在當前環境(context)執行。我發現有些人在讀代碼的時候,對變量或函數的取名不在意。poll是輪詢的意思,通過名稱就能猜到要完成的功能。有關函數WM_operator_poll
  invoke 就是函數調用了。

  因為是static函數,所以只需要在本文件內搜索調用處,其他文件是不能調用的。這也算是一種搜索技巧。

static int wm_splash_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *UNUSED(event))
{
    UI_popup_block_invoke(C, wm_block_create_splash, NULL);
    
    return OPERATOR_FINISHED;
}

  C返回 OPERATOR_FINISHED 的狀態,對應 Python 返回 {"FINISHED"}。
  接着就是 wm_block_create_splash 函數了,畫出Splash的地方,Splash上面的跳轉鏈接也寫在這個函數里。
// blender/source/blender/windowmanager/intern/wm_operators.c

static uiBlock *wm_block_create_splash(bContext *C, ARegion *ar, void *UNUSED(arg))
{
    ...
    
#ifndef WITH_HEADLESS
    extern char datatoc_splash_png[];
    extern int datatoc_splash_png_size;

    extern char datatoc_splash_2x_png[];
    extern int datatoc_splash_2x_png_size;
    ImBuf *ibuf;
#else
    ImBuf *ibuf = NULL;
#endif
    ...
    
}
splash.png

  WITH_HEADLESS 名字看不懂,CMakeLists.txt 里解釋說是不帶圖形界面的編譯(渲染農場,服務端模式),默認是關閉的。
  這里引用到了 splash.png 的圖,工程datatoc用來將splash.png圖片資源轉換成長度datatoc_splash_png_size 和二進制的dump datatoc_splash_png,上面講過。

  有關函數wm_operator_invoke,在 source/blender/windowmanager/intern/wm_event_system.c,調用棧是這樣的:
int main(int argc, const char **argv)
  WM_main(C);
    WM_init_splash(C);
      WM_operator_name_call(C, "WM_OT_splash", WM_OP_INVOKE_DEFAULT, NULL);
        WM_operator_name_call_ptr(C, ot, context, properties);
          wm_operator_call_internal(C, ot, properties, NULL, context, false);
            wm_operator_invoke(C, ot, event, properties, reports, poll_only);

static int wm_operator_invoke(
        bContext *C, wmOperatorType *ot, wmEvent *event,
        PointerRNA *properties, ReportList *reports, const bool poll_only)
{
    ...
    
    if (op->type->invoke && event) {
        wm_region_mouse_co(C, event);

        if (op->type->flag & OPTYPE_UNDO)
            wm->op_undo_depth++;

        retval = op->type->invoke(C, op, event);
        OPERATOR_RETVAL_CHECK(retval);

        if (op->type->flag & OPTYPE_UNDO && CTX_wm_manager(C) == wm)
            wm->op_undo_depth--;
    }
    else if (op->type->exec) {
        if (op->type->flag & OPTYPE_UNDO)
            wm->op_undo_depth++;

        retval = op->type->exec(C, op);
        OPERATOR_RETVAL_CHECK(retval);

        if (op->type->flag & OPTYPE_UNDO && CTX_wm_manager(C) == wm)
            wm->op_undo_depth--;
    }
    else {
        /* debug, important to leave a while, should never happen */
        printf("%s: invalid operator call '%s'\n", __func__, ot->idname);
    }

    ...
}
wm_operator_invoke

 

  invoke和exec是函數指針,要二選一填充,否則就是非法的Operator調用。上面的Splash Operator用的是invoke,它們的函數原型是:
int (*exec)(struct bContext *, struct wmOperator *);
int (*invoke)(struct bContext *, struct wmOperator *, const struct wmEvent *);

  invoke 比 exec 多出一個 struct wmEvent* 參數,返回類型是int,其實是下面的無名enum類型。
  調用exec或invoke后,都會 OPERATOR_RETVAL_CHECK(ret) 測試一下返回值。
  invoke比exec多出的一句是:wm_region_mouse_co(C, event);

/* operator type return flags: exec(), invoke() modal(), return values */
enum {
    OPERATOR_RUNNING_MODAL  = (1 << 0),
    OPERATOR_CANCELLED      = (1 << 1),
    OPERATOR_FINISHED       = (1 << 2),
    /* add this flag if the event should pass through */
    OPERATOR_PASS_THROUGH   = (1 << 3),
    /* in case operator got executed outside WM code... like via fileselect */
    OPERATOR_HANDLED        = (1 << 4),
    /* used for operators that act indirectly (eg. popup menu)
     * note: this isn't great design (using operators to trigger UI) avoid where possible. */
    OPERATOR_INTERFACE      = (1 << 5),
};

#define OPERATOR_FLAGS_ALL ( \
    OPERATOR_RUNNING_MODAL | \
    OPERATOR_CANCELLED | \
    OPERATOR_FINISHED | \
    OPERATOR_PASS_THROUGH | \
    OPERATOR_HANDLED | \
    OPERATOR_INTERFACE | \
    0)

/* sanity checks for debug mode only */
#define OPERATOR_RETVAL_CHECK(ret) (void)ret, BLI_assert(ret != 0 && (ret & OPERATOR_FLAGS_ALL) == ret)
OPERATOR_RETVAL_CHECK

  Blender的用戶偏好設置是可以保存在.blend文件里的,這是關於Splash的。
source/blender/makesrna/intern/rna_userdef.c
  prop = RNA_def_property(srna, "show_splash", PROP_BOOLEAN, PROP_NONE);
  RNA_def_property_boolean_negative_sdna(prop, NULL, "uiflag", USER_SPLASH_DISABLE);
  RNA_def_property_ui_text(prop, "Show Splash", "Display splash screen on startup");

source/blender/makesdna/DNA_userdef_types.h
  USER_SPLASH_DISABLE = (1 << 27),

 

參考:
Context + Operator = Action!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM