游戲引擎學習第228天

對上次的內容進行回顧,并為今天的開發環節做鋪墊。

目前大部分功能我們已經完成了,唯一剩下的是一個我們知道存在但目前不會實際觸發的 bug。這個 bug 的本質是在某些線程仍然訪問一個已經被銷毀的游戲模式(mode)之后的狀態,因此它屬于一種“潛在的隱患”,不是立即暴露的問題。除了這個問題外,整體系統現在運行得還不錯。

現在的流程是這樣的:我們設置了一個簡單的占位符作為標題畫面,目前是紅色背景的清屏效果,暫時還沒有真正的標題畫面美術資源。之后我們進入過場動畫(cutscene),這一部分也能正常播放,然后我們可以順利地進入游戲。

在游戲內我們可以自由地移動和進行操作,體驗完整的游戲邏輯。現在也可以順利退出游戲,回到過場動畫,而且這個過程能夠正確地重新初始化,這是之前我們想要達到的效果。不過,我們覺得可能應該退出后回到標題畫面而不是過場動畫,但這只是一個一行代碼的小改動,根據我們想要的流程隨時可以調整。

總的來說,這一整套流程都感覺良好,但還有一些細節我們需要進一步完善。其中最重要的是我們之前提到的線程 bug:如果某個模式在運行時開啟了一些線程,那么當該模式結束時,我們需要確保這些線程都被正確清理,以免出現資源未釋放或非法訪問的情況。必須要有一個機制來確保模式在結束前能確認相關線程都已退出。

另一個需要考慮的點是是否需要設計“獨立運行的模式”,例如用于預加載(prefetch)或預處理的模式。就像我們之前對過場動畫內容進行預取一樣,未來也許需要在進入某些模式前就開始異步加載資源。這可能在某些場景下很有用,比如提前加載過場動畫、場景或角色資源等等。不過這個功能是否立即實現還不確定,也許可以暫時擱置。

即便我們最終不使用地形塊(ground chunk)的合成邏輯,也不代表我們不會在別的地方需要多線程的機制,因此這方面的線程清理邏輯仍然必須完善。確保即使后面換了用途,我們的基礎結構依然是安全可靠的。

當這些核心問題解決后,我們接下來想要回到調試系統(debug code)的開發上,把之前的一些調試工具完善好,確保游戲邏輯相關的部分有良好的開發支持環境。大部分游戲邏輯已經就緒,感覺我們已經可以開始真正的玩法開發了。

希望新的一年我們能把更多時間放在實際的玩法內容制作上,而不是基礎架構的搭建。所以我們當前的目標是:

  1. 徹底解決線程相關的 bug;
  2. 讓調試系統穩定、易用;
  3. 給渲染系統加入排序邏輯,并進行一些光照方面的小優化。

當這些事情都搞定之后,我們就可以放心地進入下一階段的開發。

描述了一個任務系統中的線程問題:多個任務訪問并依賴游戲模式狀態(Game Mode),這些任務在模式切換后依然持續執行,可能引發狀態不一致或錯誤。

我們現在系統里有一個“游戲模式”(game mode)的概念,比如標題畫面、過場動畫、世界模式等,游戲狀態已經不再是一個巨大的整體結構,而是被拆分成各個模式下的不同部分,各自擁有獨立的數據結構和處理邏輯。

但在引入游戲模式之后,我們也遇到了一個具體的問題:任務系統(tasks with memory)中的一些任務可能仍在運行,特別是某個模式下啟動的任務,在該模式已經退出后卻沒有及時結束,可能繼續訪問已經無效的內存,這會引發潛在的 bug。

我們現在的任務分為幾類,比如有些任務是和資源系統相關的,用于動態從磁盤中加載資源,這是用來實現無縫加載、避免加載畫面的關鍵機制。這些任務運行期間會不斷從磁盤讀取數據,但它們與具體的游戲模式完全解耦。它們只關心“資源”本身,不關心這些資源是為哪個模式服務的,比如是為標題畫面加載,還是為世界模式加載都無所謂。因此,這類任務即使跨模式運行也不會出問題。

相比之下,問題主要出在那些直接依賴于某個特定模式數據的任務,比如說“地面塊加載任務”(ground chunk compositing tasks),這些任務的工作內容和某一個具體模式中的數據結構緊密耦合,當這個模式被銷毀后,如果這些任務仍然在運行,就會訪問無效的內存,從而出現 bug。

除此之外,還有一種任務是渲染相關的任務(render tasks),但這些任務不會成為問題的來源。因為渲染任務是在每一幀內部生成并銷毀的,它們從不會跨越幀邊界,也不會在模式切換時存活下來,因此模式切換時不需要考慮它們的影響。

所以最終我們的問題非常明確:需要一個機制確保在模式結束時,清理掉那些在該模式下啟動的、依賴該模式數據的任務,防止它們越界運行或訪問已釋放的數據。而那些與模式無關或生命周期控制明確的任務則可以忽略,不在考慮范圍之內。

為了解決這個問題,我們可以考慮兩個方向:

  1. 完全不允許任務和模式直接耦合,所有任務都要像資源系統那樣獨立。這種方式理想但實現難度大,需要重構任務邏輯,避免任務訪問模式內的數據。
  2. 或者,在模式被銷毀之前,確保所有依賴該模式的任務都已經安全退出,做到模式與其內部的任務共同生命周期管理。這種方式更實際,只要在切換模式前調用某種“等待任務完成”的機制就可以。

目前我們傾向于采用第二種方式,也就是在模式退出時強制等待其任務退出。這種方式能夠最直接地解決 bug,同時不需要大規模重構已有系統。

列出了幾種可能的解決方案,思考如何應對此類依賴模式狀態的任務問題。

我們現在真正需要關心的任務,其實只有一種:那些訪問游戲模式狀態并且會跨越多幀運行的任務。目前系統里唯一符合這個條件的是用于生成地形塊的任務(ground chunks),其他任務要么是跟資源加載相關(與模式解耦),要么是渲染任務(只在單幀內運行)。

對于這些跨幀運行且依賴游戲模式狀態的任務,我們面臨兩個選擇:

  1. 完全不允許這類任務存在,也就是說,不讓任何任務在模式中長時間存在并訪問模式內部數據。這種方式最為簡單和穩妥,但也限制了靈活性。
  2. 為這類任務提供機制以支持安全管理和清理,即在切換游戲模式時,能夠識別并妥善終止或等待這些任務完成,避免訪問已經銷毀的數據。

我們暫時不確定哪種方式更好,因為還不確定最終完成的游戲是否會真正需要這類跨幀任務。從直覺上來說,當前的地形塊生成邏輯可能最終不會使用——因為設計上動態創建大塊地形的需求并不強烈,而且實施起來復雜、回報不高,因此傾向于將其移除。

然而,即便移除了地形塊生成邏輯,我們仍可能遇到類似的需求,最典型的例子就是世界生成。假設游戲開始時,玩家需要立即進入游戲,而整個世界需要十幾秒甚至幾十秒來生成。如果我們不希望讓玩家看到加載畫面(loading screen),那么一個更合理的做法就是:

  • 讓玩家直接進入游戲并開始在初始區域活動
  • 后臺同時運行世界生成任務,逐步生成剩余世界內容

這種方式在一些游戲中很常見,比如《Don’t Starve》等游戲在啟動新游戲時會顯示“生成世界”的提示,如果我們不想要這種停頓,就必須依賴后臺任務來異步完成世界生成工作。
在這里插入圖片描述

因此,這就意味著:我們需要支持某些任務在模式內部跨幀執行,且需要為它們提供生命周期管理機制。這樣即使這些任務不是為當前模式服務了,我們也可以在切換模式前安全地終止它們,或者等它們執行完。

總結來說,我們決定支持這類任務,即便移除了當前的地形塊生成系統,也仍然有其他場景需要它,比如異步世界生成。因此,我們會保留并完善這類任務的管理機制,為將來避免加載等待和提升用戶體驗做好準備。

game.h 中為 task_with_memory 添加了一個布爾字段:b32 DependsOnGameMode,用于標記該任務是否依賴當前游戲模式。

我們目前使用的任務系統中,存在“帶內存的任務”(task with memory)的概念,并且這些任務現在被保存在 transient 狀態中。在當前實現中,一共有四個此類任務在運行。

為了解決切換游戲模式時仍有任務訪問已銷毀數據的問題,我們決定給這些任務打標簽,標識它們是否依賴于當前的游戲模式。也就是說,我們需要判斷一個任務是否與當前激活的游戲模式綁定。只要我們知道這一點,在切換模式時,就可以決定是否需要清理這些任務。

我們的做法是:

  1. 在帶內存的任務結構中添加一個標記字段,類似于 depends_on_game_mode,用于標記該任務是否依賴當前的游戲模式。

  2. 在創建任務時,通過 begin_task_with_memory 函數,我們將依賴標志作為參數傳入,并將其保存到任務結構中。

  3. 在切換游戲模式的邏輯中,我們可以掃描所有活躍任務,只清除那些 depends_on_game_mode 為 true 的任務。

這是目前最簡單有效的解決方案,雖然在某些情況下會造成輕微的阻塞(同步清理任務),但對于第一步實現來說是可接受的。后續我們也可以進一步優化,比如引入一些內存管理機制,避免同步阻塞或資源浪費。

接下來是具體實施步驟:

  • 修改 task_with_memory 的數據結構,加入 depends_on_game_mode 字段;
  • 在調用 begin_task_with_memory 時,傳入是否依賴游戲模式;
  • 在任務創建時,將該值記錄到任務本體;
  • 審查當前已有任務,標記哪些是依賴游戲模式的,哪些不是。經過檢查后發現,音頻、字體、紋理貼圖等任務并不依賴游戲模式,它們的生命周期與模式無關,屬于獨立系統;
  • 唯一一個依賴游戲模式的是地形塊任務(ground chunk),它會訪問特定的模式狀態,因此需要標記為依賴;
  • 有了這些信息后,當我們切換游戲模式時,就可以清理那些依賴模式的任務,確保不會有訪問懸空數據的風險。

至此,我們完成了第一步的機制搭建,這種方式雖然基礎,但已經解決了任務依賴問題,為后續模式切換過程中的資源清理提供了良好的起點。后續我們可以再進一步引入更靈活的異步管理與內存回收策略。

在這里插入圖片描述

在這里插入圖片描述

在這里插入圖片描述

在這里插入圖片描述

game.cpp 中修改 SetGameMode,添加 Platform.CompleteAllWorkNeedToWait 的機制,確保在模式切換前等待所有相關任務完成。

我們現在已經可以在清除游戲模式(game mode)時,采取最簡單直接的方式來確保所有相關任務能夠被正確關閉。雖然這種方式可能不是最優的性能方案,但對于當前的需求已經足夠。

我們的方法是:


等待相關任務完成再清除模式

當我們調用 set_game_mode 并準備清空之前的模式數據(即清空 mode arena)時,我們會在此之前插入一個等待操作,確保所有“依賴于游戲模式”的任務都已經完成。這個等待機制的具體實現方法是使用我們已有的任務系統接口,比如:

PlatformCompleteAllWork(low_priority_queue);

這個接口會阻塞,直到所有提交到低優先級隊列的任務完成執行。


如何判斷是否需要等待

為了避免不必要的阻塞,我們不會總是等待所有任務完成。我們引入了一個標志判斷邏輯:

  1. 遍歷所有活躍的帶內存任務;
  2. 檢查每個任務的 depends_on_game_mode 標志;
  3. 如果存在任何一個任務標記為依賴當前游戲模式,則觸發等待;
  4. 否則,不需要等待,直接切換游戲模式。

示意邏輯如下:

for (int i = 0; i < transient.task_count; ++i) {if (transient.tasks[i].depends_on_game_mode) {need_to_wait = true;break;}
}if (need_to_wait) {PlatformCompleteAllWork(low_priority_queue);
}

修改調用鏈支持 transient_state

為使 set_game_mode 能夠正確判斷任務依賴,我們需要將 transient_state 傳入其中。盡管這種參數傳遞方式有些煩瑣,也許將來可以通過更高級的結構合并優化,但目前我們先按機械方式處理。

各個調用者中,比如:

  • play_title_screen
  • play_intro_cutscene
  • play_world

都需要補充 transient_state 參數,以便在切換游戲模式時能夠訪問當前任務狀態并做出判斷。


初始化邏輯調整

在游戲初始化時,我們需要保證 transient_state 已正確初始化。在主流程中加入判斷:

if (game_state.game_mode == GAME_MODE_NONE) {play_intro_cutscene();
}

通過設置初始的 game_modeGAME_MODE_NONE,可以確保 play_intro_cutscene 的調用時機在一切準備就緒之后,避免初始化未完成時訪問未定義狀態。


小結

這套機制雖然基礎,但足以解決在切換游戲模式時殘留任務訪問已銷毀數據的問題。我們做到了:

  • 明確哪些任務依賴于當前游戲模式;
  • 在切換模式前安全地等待這些任務完成;
  • 最小化不必要的阻塞;
  • 合理調整初始化流程,保證狀態一致性;
  • 建立一條清晰的調用鏈。

后續可以進一步優化內存管理結構,讓這個機制更加靈活高效。

在這里插入圖片描述

在這里插入圖片描述

在這里插入圖片描述

在這里插入圖片描述

在這里插入圖片描述

在這里插入圖片描述

在這里插入圖片描述

在這里插入圖片描述

運行游戲進行驗證,確認改動效果正確。

現在理論上我們已經一切就緒,整個流程應該可以順利運行。當前的實現基本達成了預期的目標,雖然還有其他需要處理的小細節,但先驗證核心邏輯是沒有問題的。


當前驗證流程概況

我們已經成功完成了從進入游戲、退出游戲、再重新進入游戲的流程測試。從目前的表現來看:

  • 狀態在游戲模式切換過程中得到了正確管理;
  • 依賴當前游戲模式的任務在模式切換前被正確清理或等待完成;
  • 游戲流程能夠穩定運行,無明顯異常或資源泄露;
  • 再次進入游戲后能順利加載內容,說明資源狀態也被正確重建。

接下來準備處理的細節問題

雖然當前邏輯基本沒問題,但還有一處額外的功能希望補充完善:

在某個特定的用戶行為發生時,可能還希望觸發一些附加的邏輯處理。

這個“行為”具體在當前內容中未展開,但從上下文推測,很可能是指在用戶退出、切換、或再次進入游戲時,希望執行一些額外的視覺或邏輯效果,比如:

  • 漸變動畫
  • 資源預熱加載
  • 狀態清空提示
  • UI 層級更新或過渡

這部分雖然不是核心邏輯,但確實會影響整體的用戶體驗和流程流暢度,因此后續會加入考慮和處理。


總結

目前整個系統已具備以下能力:

  • 能識別哪些任務依賴游戲模式;
  • 能在模式切換前等待或清理這些任務;
  • 狀態切換過程中邏輯清晰,資源處理得當;
  • 再次進入游戲時狀態能夠正確重建;
  • 已準備好繼續添加附加處理邏輯。

整體來看,基礎結構已經搭好,接下來可以更安心地投入到優化細節和擴展功能中。

game_world_mode.cpp 中添加了 PlayTitleScreen 調用邏輯,確保從游戲世界退出時能正確返回標題畫面。

當前邏輯運行已經大致完成,但還有一個我們希望進一步優化的細節:在退出游戲時,如果玩家沒有任何角色存活,那么我們不應該進入中間劇情(inter cutscene),而是應該直接跳轉回標題畫面(title screen),這才更符合邏輯和用戶預期。


目標行為說明

當滿足以下條件時:

  • 所有玩家控制的角色(heroes)全部死亡;
  • 游戲模式切換流程開始(例如退出當前游戲或重新加載);

我們希望的行為是:

  • 不再進入中間過渡劇情(inter cutscene);
  • 直接回到標題界面(title screen)。

當前代碼中邏輯分布位置分析

這一判斷邏輯應該被放置在與“游戲模式結束”相關的節點處,比如在 world 模式處理的邏輯中,也就是:

  • 在 world 模式檢測到沒有英雄角色時觸發;
  • 判斷角色數量是否為零;
  • 如果是零,直接切換到 title screen;
  • 否則,仍然按照原本流程進入中間劇情。

也就是說,“是否跳過中間劇情”這一決策,需要與角色存活狀態強相關,優先判斷角色是否全部死亡,再決定下一個游戲模式。


具體實現思路

  1. 在 world 模式邏輯中添加判斷:
    • 遍歷所有角色;
    • 判斷是否全部死亡或角色列表為空;
  2. 如果是,則:
    • 調用 SetGameMode 或相關方法切換到 title screen;
  3. 如果不是,則:
    • 繼續執行現有流程(播放 inter cutscene)。

總結

我們通過邏輯判斷是否有存活角色來決定退出游戲后的流程走向。如果沒有任何角色存活,則直接回到標題畫面,這更合理也更具用戶引導性。同時該邏輯嵌入位置明確、影響范圍集中,適合局部調整而不會引發系統性副作用。

這樣做能夠有效提升玩家體驗,避免在失敗狀態下仍被迫進入劇情流程,從而保持流程的自然性與邏輯性。
在這里插入圖片描述

在調試器中發現現在我們可能在清零一大塊內存,運行游戲并使用 Debug -> Break All 功能驗證該行為。

我們之前做了一些修改,其中之一是啟用了在內存分配時自動清零(clear-to-zero-on-push),這個操作表面上是無感的,但現在注意到一個新的問題:在從標題畫面進入游戲后,按下 F5 進行重載時,黑屏淡出動畫的啟動變慢了。這個行為與過去相比是新出現的,因此我們開始懷疑這個問題與清零有關。


問題分析

目前的內存分配策略中,當我們申請一個新的內存塊時,系統會自動將其內容全部置為零。這原本是為了避免使用未初始化內存帶來的不確定性,但有些時候這反而會帶來性能負擔,尤其是在處理大規模內存塊的時候。

比如現在加載游戲過程中的某個操作正在分配一大塊內存用于創建 ground buffer(地形緩存),由于啟用了自動清零,這些 buffer 全部被強行清零,而這在很多情況下其實是不必要的。因為這些內存稍后很可能會被立即覆蓋或者由圖像數據填充,不需要先將其歸零。


調試驗證

為驗證這個假設,我們使用了一個調試技巧:在運行游戲時使用 Debug Break All(中斷所有線程),觀察當前代碼在執行什么。當中斷點命中后,我們可以很明顯地看到,在創建空 bitmap 的過程中,程序遍歷了所有 ground buffer,并對它們執行了清零操作。這個操作是在申請這些內存之后立即進行的,并不是因為需要清零,而是因為清零變成了默認行為。

從棧調用路徑來看,這一切是由我們引入的“自動清零內存”策略觸發的。這也解釋了為什么 F5 后延遲增加——系統正在無意義地清零一大片數據。


優化建議

為了修復這個問題,我們需要:

  1. 識別不應自動清零的內存分配: 比如 bitmap buffer 或者 ground buffer 這類分配后立刻會被覆蓋寫入的內存塊。
  2. 在內存申請接口中提供標志位,用于顯式控制是否執行清零操作。
  3. 在分配這類內存時傳入不清零的參數,從而跳過這一冗余操作。
  4. 保留默認清零行為,但只對真正需要初始化為零的情況使用。

這樣,我們既保留了清零機制帶來的安全性,又避免了性能浪費。


總結

當前的問題是由于我們統一啟用了內存自動清零機制,導致某些本不需要清零的大塊內存也被強行初始化,從而帶來性能問題。我們通過調試確認了這一點,下一步需要在內存分配系統中加入更細粒度的控制邏輯,確保只在真正需要時才進行清零,從而提升加載效率,避免不必要的延遲。
在這里插入圖片描述

game.h 中引入新結構 arena_push_params,允許我們在調用 Push 系列函數時指定是否清零內存。

我們為了在圣誕假期前徹底解決這個內存清零導致性能下降的問題,決定修改內存分配(Push)邏輯,讓它更加靈活、可控。目前內存系統默認分配時會自動清零,這在大多數情況下是安全的,但在處理大塊數據(例如圖像緩沖)時則可能浪費大量計算資源。因此現在要對 Push 操作進行重構。


優化目標

目標是將內存對齊方式(alignment)和是否清零(clear to zero)這兩個設置項從硬編碼中解耦出來,改為通過結構體傳參,以實現更細粒度、更具可擴展性的控制。


方案設計

引入一個新的結構體,比如叫 ArenaPushParams(暫稱為內存分配參數),它包含:

  • flags:例如 ArenaFlag_ClearToZero,表示是否需要清零;
  • alignment:表示內存對齊的方式,用于優化內存訪問性能。

然后為這類結構體定義一個默認生成器函數 DefaultArenaPushParams(),該函數返回默認清零且默認對齊的參數。后續每次執行內存 Push 操作時都將使用 ArenaPushParams 作為參數,代替之前獨立傳入的 alignment 和 flag。


實現邏輯

新的 Push 實現流程如下:

  1. 每個支持 Push 的內存操作函數都接受一個 ArenaPushParams 結構體;
  2. 默認行為使用 DefaultArenaPushParams()
  3. 在執行 Push 操作時,讀取參數:
    • 如果參數中包含 ArenaFlag_ClearToZero,則執行內存清零;
    • 否則跳過清零;
  4. 使用 params.alignment 進行內存對齊操作;
  5. 整個過程邏輯更清晰且支持擴展。

例如:

if (params.flags & ArenaFlag_ClearToZero) {ZeroMemory(...);
}

優化效果

這樣設計有幾個好處:

  • 靈活性提升:可以根據不同場景決定是否清零,避免非必要的大規模 memset;
  • 默認行為簡潔:使用默認參數即可獲得最安全的做法;
  • 接口統一:所有 Push 接口傳入結構體,邏輯清晰,方便擴展;
  • 性能優化:對性能敏感的部分可選擇不清零,節省大量 CPU 時間。

后續應用

現在已經重構完成,可以在如 GroundBuffer 等大型內存分配場景中使用 ArenaPushParams 顯式指定不清零。比如:

ArenaPushParams params = { .flags = 0, .alignment = 16 };
PushMemory(arena, size, params);

這樣分配時就會跳過清零過程。


總結

我們通過引入 ArenaPushParams 結構體,統一管理內存分配時的參數設置,成功將內存對齊與是否清零的控制權從底層邏輯中提取出來。這不僅提升了代碼可讀性和擴展性,還為后續進行更精細的性能調優提供了基礎。最終目標是在不犧牲安全性的前提下,提高整體運行效率,尤其在進入游戲場景等涉及大量數據初始化的關鍵路徑上。
在這里插入圖片描述

在這里插入圖片描述

在這里插入圖片描述

game.h 中進一步引入 AlignNoClearAlign,用于更靈活地控制內存對齊與清零行為。

我們現在可以創建一套自定義的內存分配參數,使內存分配操作既可讀又直觀,清楚地表達每一次分配操作的具體意圖。這套機制圍繞 ArenaPushParams(內存分配參數結構)進行擴展,結合對齊方式和是否清零的設置,極大提升了代碼的靈活性和清晰度。


自定義分配參數的創建

在默認分配參數 DefaultArenaPushParams 的基礎上,我們擴展了新的自定義函數,比如:

  • AlignNoClear:指定對齊方式但不清零;
  • AlignClear:指定對齊方式并進行清零。

這些函數根據實際需求靈活構建參數結構,開發過程中調用者只需選擇合適的配置函數,即可完成清晰明確的內存分配。


實現思路

每個自定義函數內部會創建一個 ArenaPushParams 實例,設置所需的對齊方式及是否啟用清零標志。例如:

ArenaPushParams AlignNoClear(u32 alignment) {ArenaPushParams params;params.flags = 0;params.alignment = alignment;return params;
}

通過這種方式,我們可以快速構建出符合語義的內存分配邏輯。


可讀性與靈活性提升

通過這種設計,調用 PushMemory 時不再是模糊的數值參數,而是直觀的表達式,比如:

PushMemory(arena, size, AlignNoClear(16));
PushMemory(arena, size, AlignClear(16));

這樣的調用方式在閱讀和維護代碼時,能立刻明白內存塊將會如何被處理(是否清零、是否對齊)。


清理冗余邏輯

由于內存是否清零現在可以通過分配參數控制,因此可以移除之前專門處理清零的邏輯函數,比如 ClearBitmap() 等。過去調用時需要手動清空圖像緩沖區,現在只需調用 PushMemory 并傳入相應參數即可自動完成,無需額外清理函數。

例如:

bitmap.data = PushMemory(arena, size, AlignClear(16));

意味著自動對齊并清空該圖像數據。


靈活配置選項

我們還考慮支持通過布爾值方式進一步簡化選擇邏輯,例如:

ArenaPushParams ChooseAlignedClear(bool shouldClear) {return shouldClear ? AlignClear(16) : AlignNoClear(16);
}

這樣在運行時可以根據條件選擇是否清空內存,大大增強了代碼靈活性。


總結

我們成功實現了一種結構化、清晰、可讀性強的內存分配參數機制,使內存管理更具語義性和可擴展性。通過引入 ArenaPushParams 結構以及一系列配置函數,我們擺脫了原本硬編碼和重復清理邏輯,實現了對內存分配行為的完全控制。這一機制將廣泛應用于如圖像數據、音頻緩沖等高頻率內存操作中,為后續優化提供了堅實基礎。

在這里插入圖片描述

在這里插入圖片描述

在這里插入圖片描述

在這里插入圖片描述

在這里插入圖片描述

在這里插入圖片描述

將這些新功能應用到所有需要它們的 Push 調用中。

現在的任務是檢查整個代碼中所有使用內存分配(push)的地方,并逐一判斷是否應該使用新加入的參數配置(比如是否清零、對齊方式等),確保這些內存分配操作充分利用我們新引入的功能,達到性能優化和邏輯明確的目的。以下是具體梳理和處理結果:


總體思路

逐一查找所有使用 PushMemory 或類似 push 操作的地方,根據上下文判斷是否應清零或指定對齊,并決定使用 AlignClearAlignNoClear 或其他組合函數,提升效率同時保持邏輯安全性。


分析與分類

保持清零的情況(默認 AlignClear*

這些內存通常用于數據結構初始化,為避免臟數據殘留,保留清零邏輯:

  • 地面緩沖區數組(Ground Buffers):體積不大,繼續清零安全穩妥。
  • 游戲資源(Game Assets):初始狀態明確,必須清零。
  • 場景模擬區(Sim Region):涉及大量狀態變量,保留清零以防遺漏。
  • 調試系統結構體(Debug Info):不涉及性能敏感,清零更保險。
  • 世界創建(Create World)、實體塊(Entity Blocks)、碰撞規則表(Collision Rules):這些結構可能包含很多未初始化字段,清零更穩妥。
  • 渲染組結構體(Render Group Structs):初始狀態必須明確,保留清零。
禁用清零的情況(使用 AlignNoClear

這些內存塊分配后立即被全部寫入,清零是多余的開銷:

  • 精準數據塊(Precise Where):分配后立即完全覆蓋,不需要清零。
  • 加載任務結構體(LoadAssetWork):所有字段都在后續賦值。
  • 整個資源存儲(Asset Store):大體積結構體,每次重新分配前都會完全重寫,清零開銷極大。
  • 文件讀取緩沖區(Asset File Arrays):數據讀取后會立即填充整個結構,清零沒有必要。
  • 渲染系統的緩沖區(Flush Buffers):每幀刷新,內容會被覆蓋。
  • 最大緩沖區設置(Max Buffer Size):作為容量值傳遞,無需清零。
    在這里插入圖片描述

在這里插入圖片描述

在這里插入圖片描述

視情況處理的情況(默認保留清零)

部分結構雖然可能會被覆蓋,但為防止遺漏某些字段或防止未初始化訪問,選擇保守處理:

  • 標簽數量(Tag Counts)、資源計數(Asset Counts):結構較小且初始化過程可能不完整,保留清零避免風險。
  • 創建字體、位圖(PushFont、PushBitmap 等):雖然不是性能瓶頸,保留清零更安全。
  • 播放剪輯數據(Cutscene 等):無性能敏感需求,清零無礙。
  • 簡單碰撞信息結構體(Collision Layers Struct):結構明確但初始化分布不一,清零更穩妥。

遺留和排查中斷點

  • 所有非真正意義上的內存分配(如函數名中含 push,但不涉及內存)的部分全部排除。
  • 部分調用者邏輯復雜(如從文件讀取失敗的情況下是否影響內存內容),默認采取不清零處理并觀察后續效果。

整體評估與優化方向

  1. 性能提升:最大收益來源于跳過對大結構體的清零(如資源存儲區、渲染緩沖等),避免了大量無意義的內存操作。
  2. 邏輯安全:對于不確定是否會完全初始化的結構體,仍然保留清零邏輯,保持代碼健壯性。
  3. 代碼可維護性:通過使用 AlignClear()AlignNoClear() 這類函數讓分配意圖一目了然,極大提高了可讀性與未來可維護性。

下一步計劃

繼續向下審查代碼中的 push 使用位置,確保全部更新為新參數方式,并根據實際運行中的性能數據進一步調優,可能還需加入運行時檢查機制來標記未完全初始化的數據使用風險點,從而形成更完整的內存管理系統。

最終定義 NoClear 等輔助函數,完成這批內存操作優化。

現在我們已經大致理清了哪些內存需要清零、哪些不需要清零,接下來的目標是完善并補全缺失的配置函數,使得內存分配調用時可以清晰地選擇是否清零、是否對齊等行為。其中一個尚未定義的配置是“不清零”(NoClear),現在要對它進行定義和實現,同時優化其它配置函數的行為,使它們更加通用和一致。


主要改動和設計意圖

定義 NoClear 配置
  • NoClear 是一個簡單的配置,表示內存分配時不進行清零操作。
  • 實現方式是基于默認的參數配置(DefaultArenaPushParams),然后移除清零標志(ArenaFlag_ClearToZero)。
  • 這種方式保證默認配置若在未來增加其他 flags,這些自定義配置仍然可以繼承新特性,具有更好的前向兼容性。
ArenaPushParams NoClear()
{ArenaPushParams result = DefaultArenaPushParams();result.Flags &= ~ArenaFlag_ClearToZero;return result;
}

對其它配置函數的統一化處理

  • 比如 AlignNoClear(alignment)AlignClear(alignment),都可以通過調用 DefaultArenaPushParams() 并覆蓋對齊和 flags 實現。
  • 所有配置函數都統一先從 DefaultArenaPushParams() 拷貝,然后只修改自己關注的部分(如清零與否、對齊值)。
  • 好處是:如果默認配置更新,比如加入新的 flags 或行為變更,這些配置函數無需改動,依然自動獲得新的默認行為。

例如:

ArenaPushParams AlignClear(u32 alignment)
{ArenaPushParams result = DefaultArenaPushParams();result.Alignment = alignment;result.Flags |= ArenaFlag_ClearToZero;return result;
}
ArenaPushParams AlignNoClear(u32 alignment)
{ArenaPushParams result = DefaultArenaPushParams();result.Alignment = alignment;result.Flags &= ~ArenaFlag_ClearToZero;return result;
}

小修正

  • 在插入一些邏輯塊的地方,有寫法需要加括號以保證優先級正確,比如 InsertBlock(...) 這一處被指出存在括號缺失,已經修正。

優化策略說明

  • 此方案核心理念是將所有內存分配的“參數集合”統一封裝在 ArenaPushParams 中,不再使用繁雜的參數傳遞或特例代碼。
  • 使用函數式構造器(如 AlignClear, NoClear 等)來聲明意圖清晰的內存行為,提升代碼可讀性和可維護性。
  • 通過繼承默認配置的方式來應對未來代碼的擴展或變動,使所有相關配置保持一致性和魯棒性。

結論

這一步補全并規范了配置函數體系,形成了一個統一的內存分配行為接口。今后在任何地方進行內存分配時,只需選擇合適的配置函數,就能清晰控制是否清零、是否對齊等行為,同時也便于調試和維護。代碼結構也更加穩健,具備了更強的擴展能力。

再次運行游戲,發現現在游戲運行明顯更快、更流暢。

現在整體系統狀態已經得到了明顯改善。

通過這次對內存分配系統的整理,我們對 Arena 內存推送操作做了統一封裝,并增加了更細粒度的控制,例如是否清零、是否對齊等參數。這種設計使得內存管理的表達更清晰、更易維護,代碼邏輯也更易于閱讀。

我們逐個檢查了所有使用 push 操作的位置,根據具體用途決定是否需要清零,成功避免了一些不必要的大規模內存清零操作,特別是某些占用大量內存但會立即被覆蓋的數據結構,不再執行多余的初始化,從而提升了整體運行效率。

完成這些修改后,我們對程序進行了測試,運行效果更加流暢,反應明顯更快,性能得到了切實改善。這個優化本身計劃已久,如今終于落實完成,達到了預期目標。

現在整個系統的內存分配邏輯更健壯、更可控,同時也為后續的優化和擴展打下了堅實基礎。整體感覺狀態非常好。

探討了幾個后續優化方向,包括將內存分配直接傳遞到底層操作系統、讓調試系統使用獨立的動態內存分配系統,以及優化渲染器的排序邏輯。

目前我們還有大約二十分鐘的時間,正在考慮在這段時間里進行什么樣的工作。

一個備選方案是讓內存 Arena 的設計深入到底層操作系統的內存管理機制,實現一種可以動態增長的 Arena,即在 Arena 空間不足時,操作系統可以繼續分配更多內存來擴展它。這種設計的動機有幾個方面:

  • 支持大世界或大內存需求的運行場景:比如在 PC 上運行的大型游戲世界,借助虛擬內存,我們希望允許系統盡可能使用機器上的可用內存。
  • 適應平臺差異:比如在內存固定的嵌入式平臺(如 Raspberry Pi)上,仍然使用固定大小的 Arena;而在 PC 上可以使用可增長的 Arena。
  • 更好地分離調試系統的內存需求:調試系統不應該與游戲主內存競爭資源。它可以使用操作系統分配的獨立內存池,不受限制地擴張,因為調試工具不會出現在最終用戶的版本中。

不過,考慮到剩下的時間可能不足以完成該功能的實現,因此決定暫時不進行這項工作。

接著開始思考可以在當前時間內完成的其他小任務。一個候選項是修復渲染排序相關的問題。

在渲染系統中,存在一些與排序有關的問題,目前可能還沒有真正實現排序機制。計劃是先實現一個非常簡單的排序算法(比如冒泡排序)作為占位,等到假期結束回來之后,再用更加高效的排序算法替換它。

這個任務的難點并不在排序算法本身,而在于需要重構 push buffer 的結構,以支持高效排序。這就需要考慮如何組織 buffer 中的數據,使其可以按需提取、排序、重新寫入或處理。這部分結構設計可能比排序本身更復雜,所以也許并不適合在這段較短的時間內處理。

因此,目前處于一個評估階段:在剩余時間里是否能完成一些簡單但有意義的工作,還是應該等待更完整的時間段再著手較大的系統重構。還在權衡和思考中。

查看當前渲染系統的具體實現邏輯,分析其工作方式。

目前沒有更好的想法要做什么,所以我們決定先看看當前的渲染流程,進行一些探索和分析。

目前渲染的整體結構是這樣的:我們采用**瓦片化渲染(Tiled Render)**的方式,每幀渲染時會調用 TiledRenderGroupToOutput,這個函數負責把渲染任務分發到多個線程中。平臺層則會負責實際調度這些線程并執行任務。每個線程會調用具體的渲染執行函數。

具體的渲染工作函數是 DoTiledRenderWork。這個函數是多線程渲染的核心,它會被各個線程調用以并行完成一幀的渲染輸出。

在這個函數中有一個有趣的現象:我們在執行渲染時仍然使用了偶數行與奇數行交替的處理方式(即線程之間以交錯的 scanline 分配渲染任務),這個邏輯最早是為了支持超線程(Hyper-Threading)優化設計的,但實際上一直沒有真正落地和調優。

回顧來看,雖然渲染部分并沒有進行特別深入的系統級優化,但通過一些簡單的優化策略,我們已經能夠在 1080p 分辨率下穩定運行在 60 幀每秒,這說明現代機器性能的確非常強勁,因此很多復雜的優化暫時并不是瓶頸。

不過,這種 scanline 交錯的方式如今可能已經成為負擔,有可能降低了性能,因為線程調度和緩存使用可能不再符合現代 CPU 的訪問模式。因此考慮是否有必要保留這個邏輯,或者嘗試做個實驗——直接去掉交錯處理,看看是否會帶來更高的幀率。

在這里插入圖片描述

在這里插入圖片描述

考慮移除「掃描線(scanlines)」的概念以簡化渲染邏輯。

當前渲染流程中存在一個令人發笑的遺留設計:我們保留了“奇偶行交錯處理”的概念,即將渲染任務分配成奇數行和偶數行分別由不同線程處理。然而這個機制從來沒有被真正使用過,也不太可能會被使用。

其原因主要在于多線程調度的不確定性——在任務隊列中并不清楚哪個超線程(Hyper-Thread)會在何時取走一個任務,因此無法實現理想中的交錯調度邏輯。此外,如果沒有明確的同步機制,也很難保證渲染效率反而不會因為人為干預變差。

因此,這段邏輯看起來就顯得很“愚蠢”,毫無實際意義,還可能帶來不必要的復雜性。于是我們決定徹底移除這套奇偶行渲染交錯邏輯,簡化系統,避免對性能造成影響。

在做這些調整之后,我們計劃測量當前的幀率表現,了解真實的運行性能。下一步準備在 Henley 環境中重新構建項目(build),以確保系統能夠正確運行并觀察性能指標。這個過程也是為了確認:在去除多余邏輯后,渲染系統是否變得更高效、響應更快。整體來看,簡化渲染路徑、移除無用機制,有助于進一步提升系統穩定性與可維護性。

啟用 GAME_INTERNAL 宏以便啟用內部調試邏輯。

我們現在打算按順序構建并啟動系統,同時開啟內部調試模式(internal),因為據我們記得之前的調試快捷鍵(比如 F5)依然可以正常使用。當前的限制主要是因為我們還沒完成字符串處理相關的熱重載(Hot Code Reloading)功能,暫時不支持完整熱加載。

接下來我們嘗試構建項目時出現了一個問題:get_arena_size_remaining 這個函數被調用時沒有傳入合適的構造參數,提示“沒有合適的構造函數”。根據上下文,這個函數需要一個對齊參數(alignment),而當前傳入的是默認值 1

為了解決這個問題,我們打算使用一個帶對齊設置的結構,比如用 no_clear(1)align_no_clear(1),也就是調用對應的內存分配器構造,傳入所需的對齊參數。這種方式可以顯式指定內存分配時的對齊方式,而不需要依賴默認行為。

總的來說,我們通過以下幾點推進系統構建與調試:

  1. 確認內存分配器的使用方式:特別是傳入對齊值時要使用合適的接口,避免出錯。
  2. 臨時繞開熱重載相關功能:由于字符串系統尚未完成,熱加載暫時不可用,但這不影響正常調試功能的運行。
  3. 保持調試環境正常運行:通過舊的 F 系列快捷鍵(例如 F5)繼續支持調試工作流程。

這一部分的目標是保證構建過程可以順利進行,為后續更復雜的系統更新奠定穩定的基礎。

運行游戲,觀察最后一幀的渲染時間。

我們現在完成了渲染流程的基本啟動,從顯示結果來看,在播放過場動畫時渲染速度明顯變慢,這是意料之中的情況,因為在該階段我們存在大量的過度繪制操作(overdraw),占用了相當多的資源。

接著,我們決定臨時移除掉用于幀率檢測的“鎖定查找”(lock lookup)部分,以便更準確地觀察當前幀渲染表現。在代碼中,我們定位到 handmade 文件夾下的相關邏輯,找到了幀率顯示(frame timing)的實現部分。這一部分通過啟用 real VBlank 支持來計時。我們之前是關閉了它的,所以現在手動啟用了幀率顯示功能。

當前測試顯示的幀率大致在 33-36 FPS 之間,這個幀率只是大致參考,因為我們當前并沒有進行實際的優化,也沒有進行系統性的性能分析。因此這些數據不能作為最終性能評估的依據,只是用于驗證更改后不會帶來災難性的性能下降。

我們設置幀率顯示的主要目的是在修改邏輯(例如去除偶數/奇數掃描線交替渲染)后,快速驗證其是否對性能造成了明顯影響。因為那段邏輯(even/odd alternating render lines)目前并沒有真正發揮作用,同時還可能帶來額外開銷,所以我們決定將其移除。

但在移除之前,必須確認這一更改不會誤傷性能,因此幀率顯示的作用就在于——在我們嘗試更改后,能夠看到幀率是否下降顯著,確保我們不是因為理解錯誤或者未曾察覺的問題,導致幀率嚴重下滑。

總結如下:

  1. 當前渲染正常運行,過場動畫期間幀率下降屬于正常情況
  2. 啟用幀率顯示,當前幀率維持在約 33-36 FPS,用于參考
  3. 移除偶數/奇數交替渲染邏輯,因其未實際啟用、可能造成性能浪費
  4. 在修改渲染邏輯前確保幀率穩定,防止誤操作導致性能劣化
  5. 目前還未開始正式優化,只是為了驗證現有結構的基礎表現

接下來可以繼續推進性能結構優化或進一步清理冗余渲染邏輯。

game_render_group.cpp 中移除了「奇數/偶數幀」的渲染邏輯,簡化代碼。

我們準備徹底移除之前實現的“偶數/奇數行交替渲染”邏輯,這部分原本是為了在多線程或超線程渲染時嘗試減少線程之間的緩存行爭用(cache line contention),但由于最終并未真正利用該機制,現在看來它反而可能帶來了不必要的復雜性與性能開銷,因此決定將其完全去除。

首先,我們定位到 DrawRectangleQuickly 函數,該函數是當前優化過的繪制矩形的方法,它接受一個參數 even,而我們現在要做的就是清理掉相關的 even 邏輯。

我們檢查 even 的作用,發現它只是簡單地控制繪制起始行的位置,如果處于“奇數”模式,它就會把起始 Y 坐標向下偏移一行。這種方式本質上是跳過偶數或奇數行的一種變通處理。但由于我們已經不再需要這種交替掃描處理方式,因此這些邏輯可以直接刪除。

修改點如下:

  1. 移除對 even 參數的依賴:將所有傳入 DrawRectangleQuicklyeven 參數刪除;
  2. 處理循環中的行步進:原先由于跳行處理,需要以 2 * BufferPitch 的步長來推進繪制,現在只需要用 BufferPitch 作為步進;
  3. 刪除與 even 有關的條件判斷與偏移:包括任何 if (Even)if (!Even) 的判斷邏輯;
  4. 檢查函數定義和調用處的一致性:移除函數聲明中的參數,同時更新所有調用該函數的地方,確保不再傳入 even

調整后,循環可以逐行完整地繪制,不再跳行,步進統一,結構更加簡潔。

此外,我們也驗證了是否預定義了這個版本的 DrawRectangleQuickly,確認它是否為預編譯優化版本,并確保函數簽名在修改后仍然保持一致性。

總結如下:

  • 刪除了不再使用的 even 邏輯;
  • 將渲染步進從 2 * BufferPitch 改為 BufferPitch
  • 簡化了 DrawRectangleQuickly 函數的定義與調用;
  • 清理了相關冗余邏輯,統一繪制流程;
  • 為后續性能評估和渲染模塊優化打下了基礎。

這一更改能讓渲染過程更加高效、結構更清晰,并消除了潛在的性能損耗點。下一步可以觀察幀率表現,確認改動未帶來負面影響。

在這里插入圖片描述

在這里插入圖片描述

在這里插入圖片描述

在這里插入圖片描述

運行游戲,確認移除后依舊渲染正常、效果良好。

我們完成了修改,現在來看看運行效果如何。

可以看到,渲染運行得非常順暢,表現非常出色,整體效果非常漂亮。畫面平滑,沒有出現任何明顯的問題或性能下降的跡象。這說明我們所做的對渲染流程的優化和重構是成功的。

特別是我們剛才移除了關于“偶數/奇數行渲染”的冗余邏輯,并統一了行步進的處理方式,使得繪制過程變得更加直接和高效。這一改變不僅沒有引發任何渲染錯誤,反而讓整個系統看起來更加整潔和可靠。

整體效果自然、無縫,可以說整個優化過程是成功的,達到了預期的目標。系統在功能和性能上都沒有受到負面影響,反而顯得更加強健,這次的修改非常值得肯定。

game_optimized.cpp 中將 Y 方向上的遞增從 2 改為 1,提高精度或渲染密度。

我們注意到當前的渲染效果并不理想,說明之前的修改還存在問題。雖然最初看起來很順利,但細節上還是出現了遺漏。

為了查找原因,我們決定放慢節奏,逐步排查問題。首先確認剪裁區域(clip rect)設置是正確的,因為偶數和奇數行的邏輯是單獨處理的,與當前的區域無關,所以裁剪邏輯依舊保持正確。

渲染流程中,只涉及兩個主要函數:DrawRectangleDrawRectangleQuickly。這兩個函數理論上在去掉“偶數/奇數”邏輯后,應該只進行一次簡單的 Y 方向遞增,邏輯是直截了當的,沒什么復雜的地方。

但我們意識到一個關鍵的錯誤:我們在其中一個函數里忘記修改 Y 坐標的遞增方式,仍然保持了原來“每次增加 2”的做法。由于先前的偶數/奇數行分離渲染依賴這個步長為2的方式,現在已經取消了這種模式,步長理應回到每次增加1(即處理每一行),否則會導致跳行渲染,從而表現出錯誤或不完整的圖像。

這個遺漏正是當前渲染異常的根源所在,修復這個邏輯后,渲染應該會恢復正常。我們接下來將修正這一點,確保 Y 坐標按照正確的步幅進行遞增。

再次運行游戲,發現性能似乎又略有提升。

我們完成了對偶數/奇數行渲染邏輯的移除,并再次進行了測試。從直觀效果來看,渲染表現并沒有明顯變化,整體顯示結果與之前基本一致,看起來沒有任何問題。

雖然我們沒有進行精確的時間測量,但感覺幀率確實有輕微提升,說明去掉該邏輯至少在性能上沒有造成負面影響。也就是說,即便沒有明確的數據支持,從目前的觀察來看,這個更改是安全的,可以保留。

我們認為繼續使用原先的“交錯渲染”方式(偶數/奇數分開渲染)并不值得,邏輯復雜而且無法帶來實際性能收益,反而可能引起維護上的麻煩。去掉這部分內容之后,整體代碼變得更清晰,也更直觀。

當然,我們也不排除未來某些情況下這個交錯渲染思路可能重新變得有價值,但目前而言,這種實現看起來只是個多余的負擔。

總的來說,現在的判斷是:去掉偶數/奇數邏輯是一個合理、穩妥的優化步驟,既簡化了代碼,又沒有實質性性能損失。我們接下來將保持這一簡化狀態,繼續向后推進渲染系統的其他優化部分。

思考如何更清晰地表示當前渲染系統到底在排序哪些元素。

目前,我們的渲染流程中所有圖元的繪制順序是按照它們被壓入 push buffer 的順序來進行的,也就是說,哪個先進入緩沖區,就先繪制哪個。但為了實現正確的圖層順序(例如前景遮擋背景),我們必須對這些圖元進行排序,而不是盲目地按照順序直接繪制。

為了解決這個問題,我們需要一種能夠對圖元進行整體訪問并比較它們之間關系的方法。也就是說,我們必須擁有一個支持“隨機訪問”的圖元列表,從而在渲染前對其進行排序,比如按照深度(z 值)或其他優先級信息排列。

現在 push buffer 中的結構是“緊湊打包”的,也就是說不同類型的圖元大小可能不同,所以我們通過“基址 + 偏移”的方式向前推進。如果我們想對其排序,這種結構會帶來不便,因為沒法直接用統一接口比較它們。

所以我們在考慮兩個可能的優化方向:


1. 統一結構體大小

如果所有的圖元類型最終都可以轉化為一個“固定大小”的結構體,那我們就可以把整個 push buffer 視為一個“數組”,這樣可以原地排序。這種方式在實現排序邏輯時會更直接、方便,比如可以直接使用冒泡排序或其他快速排序方法。


2. 雙階段處理流程(更推薦)

  • 第一階段:遍歷整個 push buffer,判斷每個圖元落在哪個 tile(屏幕分塊)中,并將其放入該 tile 對應的渲染隊列(或數組)中。
  • 第二階段:每個線程獨立處理自己的 tile,先對 tile 內的圖元列表進行排序(只排序這個 tile 所需的部分),然后再依次繪制。

這種方式更有吸引力,原因包括:

  • 每個 tile 的圖元數量相對較少,排序成本低。
  • 各 tile 渲染互不影響,天然適配多線程架構。
  • 不需要改變 push buffer 的結構,不會影響已有渲染邏輯。
  • 可以在不影響主流程的前提下分步逐漸引入排序邏輯。

因此,最優方案可能是實現一種 tile 級別的預處理機制。我們在渲染前先“收集”出每個 tile 實際需要繪制的圖元,構成一個待渲染隊列,并對這些隊列進行排序,再執行渲染。

這將讓我們的渲染流程更具靈活性,并為后續進一步的渲染優化(比如圖元合批、透明度處理等)打下良好基礎。我們可以從構建 tile 層圖元隊列開始,逐步構建排序與繪制系統。這看起來是當前最具可行性的路線。

game_render_group.h 中為 render_group 添加 PushBufferElementCount 字段,用于追蹤被壓入渲染緩沖區的元素數量。

我們在考慮對渲染元素進行排序,為了實現這一點,首先需要一個地方來存儲這些渲染元素的相關信息。幸運的是,我們已經知道最多會有多少個渲染元素,因此完全可以分配出一塊固定大小的內存來保存這些數據。

在構造 push_buffer 的過程中,可以添加一個額外的計數機制,例如 push_buffer_element_count,每次有一個新的渲染元素被壓入緩沖區時,該計數器就加一。這樣一來,我們就能始終準確地知道當前緩沖區中到底有多少個渲染元素。

具體的實現方式如下:

  • 在調用 allocate_render_group 分配渲染組內存的時候,除了設置緩沖區的大小外,還可以將 push_buffer_element_count 初始化為 0。
  • 每當執行一次元素壓入(push)的操作時,對 push_buffer_element_count 進行自增。
  • 這個計數器在每次渲染結束調用 end_render_group 的時候也會被清零,確保下次渲染時是全新的狀態。

如此一來,在執行 detailed_render_work 的時候,我們就可以通過這個計數值提前知道本次渲染中有多少個渲染元素。接下來在構建工作結構(例如每個線程處理的 tile 對應的渲染數據)時,就可以分配出一塊臨時內存空間來保存這些數據,用于后續的排序操作。

這也就意味著,我們可以在每個 tile 所代表的渲染工作結構中,預先為排序結果留出內存區域,每個線程在工作前先把需要繪制的圖元篩選出來存進這個區域中,再進行排序并最終執行繪制。

這個邏輯實現起來清晰、可靠,同時不會影響原有 push buffer 的使用方式,非常適合在現有系統基礎上引入排序機制,為更復雜的圖層管理和渲染流程打下良好基礎。

在這里插入圖片描述

在這里插入圖片描述

在這里插入圖片描述

game_render_group.cpp 中引入新結構 tile_sort_entry,并應用于 RenderGroupToOutput 進行排序處理。

我們現在的重點是構建一個排序機制,用于在渲染流程中更好地控制渲染元素的順序。具體來說,需要實現一套排序的中間數據結構和分配機制,使得每個線程在執行 tile 渲染時,都能臨時性地擁有一塊排序緩存空間用于排序工作。整體思路清晰,并且針對不同線程和渲染路徑也做了初步的內存策略考慮。以下是詳細整理和總結:


總體目標

  • 實現渲染元素的排序功能,用于按指定順序進行輸出。
  • 渲染時,不再按照元素寫入順序繪制,而是基于 sort key 來重新排序再繪制。

數據結構設計

  • 為每個渲染元素設置一個排序結構體(如:TileSortEntry),結構包含:
    • 對應元素的索引或指針
    • sort key
    • 在 push buffer 中的偏移位置等信息
  • 對每個 tile 或整體渲染調用,創建一塊排序臨時緩存區域,用于存儲這些結構體。

內存管理設計

  • 利用臨時內存(transient arena)進行排序緩存空間分配。
  • 因為已知最多會有多少個渲染元素,所以可以一次性分配足夠大的空間:
    • PushArray() 之類的宏或函數,直接申請一段可排序的數組。
  • 結構體中新增一個字段:PushBufferElementCount,用于記錄實際元素數量,便于分配空間。

渲染流程整合

  • 無論是普通還是 tile 渲染流程,都需要支持排序。
  • 所以在 RenderGroupToOutput()TiledRenderGroupToOutput() 函數中都必須執行類似的準備邏輯:
    • 將臨時 arena 傳入,用于后續分配排序緩沖區。
    • 進行 BeginTemporaryMemory()EndTemporaryMemory() 操作來清理使用的臨時空間。

多線程 tile 渲染注意事項

  • 在多線程 tile 渲染中,每個線程應從自己的上下文或任務結構中分配排序所需臨時內存,避免沖突。
  • 排序和繪制僅作用于當前 tile 中的元素,所以只需要局部排序而非全局排序。

調試與備用邏輯

  • 在 debug 模式下也調用了 tile 渲染流程,因此需要確認調試路徑中也合理使用了臨時內存。
  • debug 模式下可以靈活使用 debug arena 或 transient arena,因為這些只存在于當前渲染周期。

結論

  • 整體上,實現渲染元素排序的機制是可行且思路清晰的。
  • 已明確元素數量可知,因此可預分配排序緩沖區。
  • 各路徑都需傳入臨時內存,以便安全分配排序空間。
  • 多線程場景下,每個任務各自處理排序內存,不會發生沖突。
  • 該方案既兼容現有架構,又具備擴展性。

如果需要進一步實現這套邏輯,可以繼續拆分出:

  • TileSortEntry 的定義
  • PushRenderElement 的結構調整
  • 排序過程(如快速排序或穩定排序)的實現接口
  • 渲染時對排序結果的遍歷與調用

需要我幫你具體寫一下這塊的偽代碼或結構設計嗎?
在這里插入圖片描述

在這里插入圖片描述

在這里插入圖片描述

在這里插入圖片描述

運行游戲進行測試,驗證這些結構和排序邏輯都正常運行。

目前為止的渲染系統改造已經完成了一個關鍵步驟:為每個 tile 的詳細渲染工作分配了專門的排序緩沖區,這為后續實現排序渲染打下了堅實基礎。下面是這段工作的詳細總結:


當前階段完成情況

  • 排序空間已就緒:每個 tile 在進行實際渲染前,都擁有一塊獨立的排序內存區域,可以存放需要排序的渲染項。
  • 架構合理:并未直接進入排序邏輯的實現,但當前的內存與數據結構設計已經為之后的排序打通了路徑。
  • 不會中斷流程:這個階段的結束點自然且平穩,沒有留下一些未解的問題,也沒有邏輯斷點,適合作為階段性收尾。

后續渲染流程的準備

  • 可以在接下來的邏輯中,將當前的渲染項數據轉換為排序項(如:填充 TileSortEntry)。
  • 排序項結構中會包含渲染項在 PushBuffer 中的偏移位置和對應的排序 key。
  • 一旦排序項數組填充完成,就可以執行排序操作,然后按序進行實際繪制。

整體渲染流程的演變

  1. 原始流程:渲染元素按寫入順序直接輸出。
  2. 當前狀態:準備好排序階段的內存和結構,但未執行排序。
  3. 未來目標:對元素排序后再輸出,實現更加合理或優化的渲染順序(例如按層深度、透明度等排序)。

后續任務方向(待完成)

  • 構造排序項填充邏輯。
  • 設計排序算法(可能是快排或穩定排序)。
  • 替換原有輸出流程,改為按排序項輸出。
  • 視情況對 debug 渲染路徑做適配。

總結

我們已經成功為渲染排序功能搭建好了基本框架和必要的內存結構,接下來的工作就是將渲染項轉換為可排序結構,執行排序后再進行輸出渲染。整體架構清晰可控,后續實現將水到渠成。雖然還需一定的實現時間,但方向明確,邏輯上已非常穩固。

需要我現在幫你規劃或編寫排序部分的實現嗎?比如偽代碼或者函數接口設計?

在這里插入圖片描述

在這里插入圖片描述

大小不夠嗎
在這里插入圖片描述

“擴展型 arena 的實現大致上會是怎樣的?”

這是對如何實現一個可擴展內存分配器(expanding arena)的詳細總結:


實現可擴展 Arena 的基本思路

核心思想非常簡單:在嘗試分配內存失敗(即當前 Arena 容量不夠)時,不再報錯或斷言(assert),而是動態向操作系統申請新的內存區域。

基礎實現邏輯:
  1. 判斷容量是否足夠
    • 原本的做法是在容量不足時直接斷言失敗;
    • 現在改為:如果容量不足,進入一個分支邏輯。
  2. 向操作系統申請更多內存
    • 使用平臺相關接口(如 VirtualAllocmmapmalloc 等)來獲取額外內存;
    • 將新分配的內存連接到當前 Arena;
    • 更新 Arena 的容量和指針信息。

這樣一來,Arena 就可以自動根據需要擴展,避免容量限制帶來的錯誤或人為干預。


臨時內存系統(Temporary Memory)的復雜性

相比之下,實現臨時內存(Temporary Memory)系統時就需要更多處理,因為它需要**可回退(rewind)**的能力。

臨時內存的特點:
  • 會在某個點開始分配臨時空間;
  • 使用完之后可以一鍵回滾,釋放所有中間的分配;
  • 因此需要保存一個快照(如 base pointer 或 offset)以便復原;
  • 在可擴展 Arena 上實現這種“回滾”行為就涉及更多結構和數據維護。

實現簡潔性

  • 主體邏輯非常簡單,是一種極易實現的內存分配策略;
  • 擴展性強,特別適用于不需要頻繁釋放的線性分配模式;
  • 唯一需要更細致處理的就是與臨時分配的配合機制。

總結

可擴展 Arena 實現極其直觀:判斷容量不足就申請新內存。唯一帶來一些邏輯復雜度的是“臨時內存支持”,因為需要保存和恢復狀態。但整體而言,整個系統設計上是非常直接、高效并易于控制的。

需要我現在給出具體的代碼框架或偽代碼嗎?可以直接用 C 或 C++ 寫出一份簡單可擴展 Arena 的例子。

“你提到的底層內存 arena 是不是其實就類似 malloc / VirtualAlloc 的機制?”

這是關于底層內存管理中 Arena 和系統默認分配器(如 mallocVirtualAlloc)之間差異的詳細總結:


內存 Arena 的本質

內存 Arena 是一種最簡單、最直接的內存分配模型,通常用于性能敏感、可控生命周期的內存分配場景。其特點如下:

  • 線性分配:從一塊連續的內存區域中按順序分配;
  • 不可自由釋放:通常不支持任意順序的釋放操作,只支持一次性全部釋放或按棧結構回滾;
  • 極高效率:由于無需處理碎片、合并、重用等復雜邏輯,性能非常高;
  • 非常適合游戲或渲染等臨時性數據構建場景

mallocVirtualAlloc 的區別

相比之下,像 mallocVirtualAlloc 則是系統層面的更復雜內存分配機制,它們具有以下特性:

  • 允許任意順序的申請與釋放
  • 支持碎片合并(memory coalescing),保持內存利用率;
  • 內部維護分配表與元信息,以便跟蹤每一塊內存;
  • 相對慢很多,因為需要處理通用性和復雜情況;
  • malloc 屬于 C 運行庫的一部分,底層可能調用 VirtualAlloc 或其他平臺 API;
  • VirtualAlloc 是 Windows 系統調用,直接控制虛擬內存頁的分配。

核心對比總結

特性內存 Arenamalloc / VirtualAlloc
分配順序線性順序任意順序
釋放方式一次性全部釋放或回滾任意釋放
內部邏輯極簡、無碎片管理有元數據管理、合并、重用邏輯
性能極高相對較慢
控制性程序員全權控制由系統/運行庫管理
應用場景臨時構建、渲染、加載階段通用應用分配需求

總結觀點

內存 Arena 并不是 mallocVirtualAlloc 的簡單封裝,而是一種更原始、更輕量、更高效的分配策略。它犧牲了靈活性和碎片管理能力,換取極致的分配速度與可預測性,非常適合底層性能優化、臨時工作空間等場景。

需要我給你畫一張對比示意圖,或寫一段 Arena 實現代碼嗎?

“我剛買了 K&R 這本書來學 C,還有推薦的資源嗎?我對網上的資源有些保留。”

我們提到了 Simon Denson、Cane、Butler 這幾個相關的資源,但除了這些之外,沒有找到其他特別有用的學習資料。我們對網上的信息向來保持謹慎態度,不會輕信網絡上的內容。因此,對于這類問題,覺得自己可能不是最合適的人選來提供判斷或推薦。

比較好的選擇是直接去請教正在直播的那群人,那邊可能會有更具權威性或者更有經驗的人來給予建議。總體而言,我們更傾向于基于實際經驗或可靠來源來判斷,而不是隨便采納網絡上的說法。

“Interlaced(交錯)和 Interleaved(交織)是一個意思嗎?”

我們討論了 “gasifier is interlaced” 中的術語差異,明確了 interlacedinterleaved 雖然聽起來相似,但含義上存在顯著區別。

interleaved(交錯) 是一個更通用的術語,表示兩種或多種事物在操作或結構中以交替方式排列或執行,比如內存訪問、數據排布等領域都可能用到。只要是交替進行的,就可以稱作 interleaved,應用非常廣泛,不局限于特定領域。

interlaced(隔行掃描) 則是一個非常特定的術語,主要用于顯示技術領域。它描述了一種圖像掃描方式:在一個幀周期內,先繪制所有偶數行(或奇數行),然后再回到頂部繪制剩下的奇數行(或偶數行),以此完成完整圖像的顯示。這種方式主要用于早期的電視和顯示器技術,用于降低帶寬需求。

因此,雖然這兩個詞都帶有“交錯”之意,但 interlaced 是 interleaved 的一種特定形式,專門用于描述顯示設備如何掃描圖像;而 interleaved 是更抽象和廣義的概念,可以用于任何需要交替處理的情境。

“在 game Hero 暫停期間我應該做些什么?”

我們在 Hammy 休息期間,有了一段思考接下來該做什么的討論。當前已經為我們安排了一個具體的任務目標:實現排序功能。相關的準備工作都已經完成,所需的結構和支持也都已經就緒。

接下來的大致計劃是這樣的:

  • 利用這段空檔時間專注完成排序功能的實現,任務已明確,目標清晰。
  • 預計花費時間大約兩周左右,可能會稍短一些,但兩周是一個比較穩妥的估計。
  • 在這段時間里,由于暫時不能在那臺機器上繼續編程,我們就可以集中精力推進項目,不受干擾。
  • 目標是在回來并恢復編程之前,讓游戲的基礎功能能夠運行起來,具體而言,是能夠完成排序流程并整合進當前渲染系統中。

這是一個十分適合獨立攻堅的階段,時間充足,任務聚焦,具備良好的實現條件。只要按部就班推進排序邏輯的開發和調試,我們完全有能力在回來之前完成這項關鍵功能,為后續開發打下堅實基礎。

“你計劃用哪種排序算法?”

我們打算從最基礎的排序算法開始實現。首先會選擇冒泡排序,這是最簡單、最基礎的一種排序方式,幾乎可以說是“最笨的那種”。不過這正是它的優點——結構清晰、實現直觀,適合作為排序流程搭建的起點。

接下來,會嘗試實現一種更復雜的排序算法,比如歸并排序(Merge Sort)。這是經典的高效排序方法,尤其適用于需要穩定排序、或者數據量較大的場景。

這么做的原因在于:排序在性能上的表現并不是絕對的。有時候,看起來簡單、低效的排序算法(比如冒泡排序),在特定的上下文中反而比“聰明”的排序方法更快。這可能跟數據的分布特性、緩存命中、排序規模等各種因素有關。

因此,我們會用實驗的方式去比較不同排序算法的性能,而不是先入為主地選擇看起來更先進的方案。通過這種方式,不僅能夠確保選擇最適合當前場景的排序方法,也能夠為后續的系統優化積累更多真實的性能數據。

可以預見,這個過程可能會有些“疼”(性能測試和調優通常伴隨著反復試驗),但這是必要的一步。我們已經準備好接受這個挑戰。

“為什么用 Windows 應用程序(WinMain)而不用標準的 main 函數?還是得手動注冊窗口類并創建窗口?”

我們選擇以 Windows 應用程序(使用 WinMain 作為入口點)而不是控制臺程序來運行,是出于界面專業性和用戶體驗的考慮。

如果將應用構建成控制臺程序,每次運行時,系統都會自動彈出一個控制臺窗口。這個控制臺窗口會始終存在,無法避免地出現在用戶眼前,這在發布游戲或正式應用時看起來非常不專業。

雖然有一些方法可以隱藏這個控制臺窗口,比如運行時查找并手動隱藏它,但那是一種多此一舉的做法,尤其是在我們根本不需要控制臺窗口的情況下。既然我們本身不打算用它來輸出調試信息或接收輸入,那就完全沒有必要使用控制臺程序入口

因此,從一開始就直接將程序構建為標準的 Windows 應用程序是最合理的選擇。這樣,用戶打開應用時只會看到我們的主窗口界面,不會出現多余的黑色控制臺窗口,整體效果更干凈、專業,也更符合最終發布的需求。

“當初為什么會加入奇偶幀交替渲染這種機制?”

之所以當初加入了交錯渲染(interleaving),是因為我們當時在考慮是否可以利用超線程來提升性能。具體設想是:讓每個超線程渲染交替的掃描線(比如一條線程渲染偶數行,另一條渲染奇數行),這樣它們在同一個核心上運行時,就能共享緩存(cache),提升數據命中率,進而提高效率。

理論上這個想法聽起來是合理的,因為兩個線程在交替渲染時可能會訪問相鄰的數據,而這些數據如果都能保留在緩存中,就能減少內存訪問延遲,提高整體性能。

然而,實際操作中發現存在一個關鍵問題——無法強制控制線程在特定的方式下調度運行。即便設計好了交錯訪問的邏輯,也無法保證兩個線程會按照預期在同一個物理核心上交錯運行。線程的調度是由操作系統決定的,我們無法精確干預,從而導致這個優化無法真正發揮預期作用。

所以最終來看,這種交錯渲染的做法并沒有帶來實質性的性能收益,反而可能讓渲染邏輯更復雜,變成了一種不必要的嘗試。簡而言之,這個優化點在缺乏調度控制的情況下是沒有實際意義的冗余設計

“每一幀都重新生成渲染線程是不是開銷過大?”

每幀都重新創建渲染線程會帶來較大的開銷,尤其是在掃描內存時,因為線程的創建和銷毀本身就需要一定的時間和資源。如果每幀都重新生成渲染線程,不僅會導致更高的資源消耗,還會增加線程調度的負擔,影響整體性能。因此,為了避免這種額外的開銷,我們選擇保持渲染線程的存在,而不是每幀都重新生成。這樣可以減少不必要的開銷,提高性能。

“你做游戲時是不是總是從軟件渲染開始?”

在游戲開發中,通常不會從軟件渲染開始。之所以選擇使用軟件渲染,是為了讓觀眾了解圖形渲染的工作原理。通過手動編寫渲染器,可以幫助人們更清楚地理解圖形如何實際運作。如果僅僅依賴現代的圖形API(如圖形庫),而沒有深入了解其底層實現,就很難真正理解圖形是如何處理和顯示的。過去,只有那些早期從事圖形編程的人,才有機會深入了解這些底層細節。因此,通過這種方式,可以讓更多人獲得對圖形渲染過程的深入理解。

“用 GDI 做動畫時,如果不用 vsync,還能做到非常平滑嗎?”

目前,關于在沒有V-Sync的情況下實現平滑動畫并不確定。隨著Windows操作系統的不斷發展,和過去的不同,現在對圖形處理的了解和技術手段也有了很大的變化。對屏幕刷新和合成器(例如箭頭合成器)并沒有深入探索過,因此對于這種特定情況的處理方法并不清楚。

對于游戲開發來說,通常來說,這并不是非常重要,因為游戲最終仍然需要通過GPU來處理圖形,即使是使用軟件渲染,仍然需要通過OpenGL等圖形API來確保能夠進行垂直同步(V-Sync)。即使沒有硬件加速的渲染過程,依然需要一些代碼來通過OpenGL實現垂直同步。因此,目前對如果不采用這種方式會發生什么,并沒有太多了解。

“如何在純 C 中 forward declare 一個 struct?‘typedef struct foo {…} foo;’ 要怎么提前聲明?”

關于如何在C或C++中前向聲明結構體,首先需要明確一點:在C語言中,結構體的前向聲明是很常見的做法,但如果要為結構體使用 typedef,通常需要一定的步驟。

要前向聲明一個結構體類型并使用 typedef,可以按照以下方式進行:

typedef struct MyStruct MyStruct;

這種方式告訴編譯器有一個結構體類型 MyStruct,但它的實際定義會在稍后的代碼中出現。這樣可以讓你在函數聲明和指針操作中使用 MyStruct,但還不需要定義它的具體內容。

完整的定義會在稍后的代碼中出現:

typedef struct MyStruct {int a;float b;
} MyStruct;

如果嘗試直接用 typedef struct 來前向聲明和定義類型(例如 typedef struct MyStruct { ... } MyStruct;),它通常會導致錯誤,因為編譯器需要結構體的完整定義來完成 typedef

“有沒有優化方案可以避免混合透明像素?”

目前在渲染過程中,并沒有對透明像素進行任何優化。當前的情況是,計算機性能非常強大,因此即使在沒有優化的情況下,渲染速度仍然能夠達到每秒 30 幀。實際上,渲染時,背景中的每一部分都被繪制了多次,可能多達十層重疊,這意味著每個像素可能會被多次重復計算和渲染。

如果需要提高效率,實際上可以通過優化顯著提高渲染速度。通過消除不必要的重復繪制和優化透明像素的處理,渲染速度可能可以提升四到五倍。然而,即便沒有這些優化,現代計算機的速度仍然足夠快,能夠確保即使是復雜的場景和動畫也能順利運行,而不需要做額外的工作,這種性能的提升令人驚訝。

“一旦實現了排序功能,你是否打算讓渲染器從前往后繪制?”

在排序實現之后,是否會改變渲染順序,改為前到后的繪制方式,是一個值得考慮的問題。的確,這個方法并不壞,之前也討論過這個想法,并且在某些情況下,可能會進行一些組織和調整。雖然目前沒有明確的決定,但這個問題值得深入思考。

“我寫一些后臺程序不想出現窗口,所以用 WinMain 再把窗口隱藏。如果用 main 啟動,有沒有辦法讓控制臺也隱藏,像任務管理器里都看不到?”

詢問的是是否有辦法在Windows應用程序中使用控制臺并將其隱藏,尤其是隱藏在任務管理器等地方。對于這個問題,詢問者似乎不確定具體想要隱藏的是什么內容。

“可以簡單介紹下你們當前的內存管理方式嗎?比如 PushArena / BeginTempArena 等。”

目前內存管理的方式非常簡單,使用的是推送內存區(push arena)和臨時內存(temporary memory)等機制。內存管理本身并不復雜,雖然手動管理內存需要編寫一些代碼,特別是在流媒體(stream)中,可能需要較多的工作,但一旦實現了這些基礎,管理內存就變得相對容易了。大部分時候,內存管理幾乎可以忽略不計,只有在特定的幾個地方才需要特別關注內存的分配與管理。

黑板講解:「內存 Arena 管理系統」

目前的內存管理方式非常簡單。我們首先分配一塊內存,假設是1GB,用于游戲的內存需求。然后,這塊內存會被劃分成不同的區域,比如一部分用于資產(assets),一部分用于游戲本身等。

我們不直接將整個1GB的內存分配給游戲,而是在啟動時將其劃分成不同的塊,這些塊稱為“永久內存區”和“臨時內存區”。接下來,當內存被分配時,我們就開始往這些塊中“堆積”數據。具體來說,內存就像一個大的緩沖區,從空開始,我們向其中不斷添加數據。

內存管理的關鍵在于“堆棧”式的操作。每次添加新的內存塊時,它會直接“壓入”棧中。當處理臨時內存時,我們會記住當前棧的位置,使用這部分內存后,回到之前的棧位置,仿佛這部分內存從未被使用過。這樣新的內存請求會覆蓋之前的內容。

總體來說,內存管理非常簡單,只是不斷推送和回退棧指針而已。沒有更復雜的操作,只有這個最基礎的過程。

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

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

相關文章

游戲測試入門知識

高內聚指的是一個模塊或組件內部的功能應該緊密相關。這意味著模塊內的所有元素都應該致力于實現同一個目標或功能&#xff0c;并且該模塊應當盡可能獨立完成這一任務。 低耦合則是指不同模塊之間的依賴程度較低&#xff0c;即一個模塊的變化對其它模塊造成的影響盡可能小。理…

L1-2 種鉆石

題目 2019年10月29日&#xff0c;中央電視臺專題報道&#xff0c;中國科學院在培育鉆石領域&#xff0c;取得科技突破。科學家們用金剛石的籽晶片作為種子&#xff0c;利用甲烷氣體在能量作用下形成碳的等離子體&#xff0c;慢慢地沉積到鉆石種子上&#xff0c;一周“種”出了一…

基于開源技術生態的社群運營溫度化策略研究——以“開源鏈動2+1模式AI智能名片S2B2C商城小程序源碼”融合應用為例

摘要 在社交媒體與電商深度融合的背景下&#xff0c;社群運營的“溫度化”成為企業構建用戶忠誠度的核心命題。本文以康夏社群運營案例為切入點&#xff0c;結合“開源鏈動21模式AI智能名片S2B2C商城小程序源碼”技術架構&#xff0c;分析其通過開源技術實現情感聯結與商業價值…

編程技能:調試01,調試介紹

專欄導航 本節文章分別屬于《Win32 學習筆記》和《MFC 學習筆記》兩個專欄&#xff0c;故劃分為兩個專欄導航。讀者可以自行選擇前往哪個專欄。 &#xff08;一&#xff09;WIn32 專欄導航 上一篇&#xff1a;編程基礎&#xff1a;位運算07&#xff0c;右移 回到目錄 下一…

從零開始學A2A二 : A2A 協議的技術架構與實現

A2A 協議的技術架構與實現 學習目標 技術架構掌握 深入理解 A2A 協議的分層架構設計掌握各層次的功能和職責理解協議的工作原理和數據流 實現能力培養 能夠搭建基本的 A2A 服務端掌握客戶端開發方法實現智能體間的有效通信 架構設計理解 理解與 MCP 的本質區別掌握多智能體協…

UE5滾輪控制目標臂長度調整相機距離

UE5通過鼠標滾輪來控制攝像機目標臂長度 , 調整相機距離 看圖就行,不多說,照著連就完事了

python的strip()函數用法; 字符串切片操作

python的strip()函數用法 目錄 python的strip()函數用法代碼整體功能概述代碼詳細解釋1. `answer["output_text"]`2. `.strip()`3. `final_answer = ...`字符串切片操作:answer[start_index + len("Helpful Answer:"):].strip()整體功能概述代碼詳細解釋1…

云服務模式全知道:IaaS、PaaS、SaaS與DaaS深度解析

云服務模式詳解&#xff1a;IaaS、PaaS、SaaS與DaaS 在當今數字化快速發展的時代&#xff0c;云計算已經成為企業和開發者不可或缺的一部分。它提供了靈活的資源和服務&#xff0c;使得用戶可以根據自己的需求選擇最合適的解決方案。本文將詳細介紹四種主要的云服務模式&#…

AIDL 語言簡介

目錄 軟件包類型注釋導入AIDL 的后端AIDL 語言大致上基于 Java 語言。AIDL 文件不僅定義了接口本身,還會定義這個接口中用到的數據類型和常量。 軟件包 每個 AIDL 文件都以一個可選軟件包開頭,該軟件包與各個后端中的軟件包名稱相對應。軟件包聲明如下所示: package my.pac…

PINN:用深度學習PyTorch求解微分方程

神經網絡技術已在計算機視覺與自然語言處理等多個領域實現了突破性進展。然而在微分方程求解領域&#xff0c;傳統神經網絡因其依賴大規模標記數據集的特性而表現出明顯局限性。物理信息神經網絡(Physics-Informed Neural Networks, PINN)通過將物理定律直接整合到學習過程中&a…

程序化廣告行業(89/89):廣告創意審核的關鍵要點與實踐應用

程序化廣告行業&#xff08;89/89&#xff09;&#xff1a;廣告創意審核的關鍵要點與實踐應用 在程序化廣告這個充滿機遇與挑戰的領域&#xff0c;持續學習和知識共享是我們不斷進步的動力。一直以來&#xff0c;我都希望能和大家一同深入探索這個行業&#xff0c;今天讓我們聚…

【ES6新特性】Proxy進階實戰

&#x1f31f;ES6 Proxy終極指南&#xff1a;從攔截器到響應式框架實現&#x1f525; 一、&#x1f4a1; 為什么Proxy是革命性的&#xff1f;先看痛點場景 1.1 Object.defineProperty的局限 &#x1f62b; // Vue2響應式實現 let data { count: 0 }; Object.defineProperty(…

c++解決動態規劃

一、引言: 在我們學習了算法之后,我們一定遇到過貪心算法。而在貪心算法中就有著這樣一個經典的例子——湊錢。 Eg: 你有面額為10、5、1的紙幣,當你買菜時需要花費26元,請問需要最少的紙幣張數是多少。 當我們用貪心算法去解決這個問題的時候,我們…

Qwen 2.5 VL 多種推理方案

Qwen 2.5 VL 多種推理方案 flyfish 單圖推理 from modelscope import Qwen2_5_VLForConditionalGeneration, AutoTokenizer, AutoProcessor from qwen_vl_utils import process_vision_info import torchmodel_path "/media/model/Qwen/Qwen25-VL-7B-Instruct/"m…

機器視覺檢測Pin針歪斜應用

在現代電子制造業中&#xff0c;Pin針&#xff08;插針&#xff09;是連接器、芯片插座、PCB板等元器件的關鍵部件。如果Pin針歪斜&#xff0c;可能導致接觸不良、短路&#xff0c;甚至整機失效。傳統的人工檢測不僅效率低&#xff0c;還容易疲勞漏檢。 MasterAlign 機器視覺對…

經典算法問題解析:兩數之和與三數之和的Java實現

文章目錄 1. 問題背景2. 兩數之和&#xff08;Two Sum&#xff09;2.1 問題描述2.2 哈希表解法代碼實現關鍵點解析復雜度對比 3. 三數之和&#xff08;3Sum&#xff09;3.1 問題描述3.2 排序雙指針解法代碼實現關鍵點解析復雜度分析 4. 對比總結5. 常見問題解答6. 擴展練習 1. …

1022 Digital Library

1022 Digital Library 分數 30 全屏瀏覽 切換布局 作者 CHEN, Yue 單位 浙江大學 A Digital Library contains millions of books, stored according to their titles, authors, key words of their abstracts, publishers, and published years. Each book is assigned an u…

地理人工智能中位置編碼的綜述:方法與應用

以下是對論文 《A Review of Location Encoding for GeoAI: Methods and Applications》 的大綱和摘要整理&#xff1a; A Review of Location Encoding for GeoAI: Methods and Applications 摘要&#xff08;Summary&#xff09; 本文系統綜述了地理人工智能&#xff08;G…

(C語言)算法復習總結2——分治算法

1. 分治算法的定義 分治算法&#xff08;Divide and Conquer&#xff09;是一種重要的算法設計策略。 “分治” 從字面意義上理解&#xff0c;就是 “分而治之”。 它將一個復雜的問題分解成若干個規模較小、相互獨立且與原問題形式相同的子問題&#xff0c;然后遞歸地解決這…

愛普生FC1610AN5G手機中替代傳統晶振的理想之選

在 5G 技術引領的通信新時代&#xff0c;手機性能面臨前所未有的挑戰與機遇。從高速數據傳輸到多任務高效處理&#xff0c;從長時間續航到緊湊輕薄設計&#xff0c;每一項提升都離不開內部精密組件的協同優化。晶振&#xff0c;作為為手機各系統提供穩定時鐘信號的關鍵元件&…