游戲引擎學習第247天:簡化DEBUG_VALUE

歡迎。關于紋理傳輸的詳細情況。

上周我們剛剛完成了紋理下載的相關工作,但實際上并沒有完全解決這個問題。問題的核心是,當前關于紋理下載的正確方式仍然存在較大的不確定性。盡管我們在進行紋理下載的工作時已有一定進展,但依舊有不少模糊的地方。

這其實是一個持續進行的過程,可以說是一個“傳奇”。我們想要展示的,是如何進行編程的過程,而不僅僅是展示編程完成后的結果。很多編程工作尤其是涉及到圖形編程的情況,常常會遇到類似的情況。大家可能會去參考一些推薦的做法,或是去查看一些最新發布的教程和視頻,但當實際動手實現時,卻往往會發現這些方法并不完全適用。有時可能會發現它們在某些場景下根本不起作用,或者其中的某些部分是錯誤的,甚至可能完全沒有提供實際可行的做法。

現在我們正處于紋理下載的一個“懸而未決”的階段,實際上這也是大部分編程工作的常態,很多時候會遇到這樣的問題,既沒有一個標準的答案,也沒有現成的解決方案可以直接拿來用。

NVIDIA 還沒有回應如何正確地傳輸重疊的紋理。

盡管我曾與 Nvidia 有過一些溝通,但目前還沒有得到關于如何在他們的顯卡上正確下載重疊紋理的明確答案。

一些人認為有一定的解決方案,但我并不確定他們的說法是否準確。因為這些人并沒有特別權威的證據來證明自己的觀點,且沒有人能夠給出確鑿的數據支持他們的說法。

重疊的 CPU 工作和 GPU 工作并不是同一回事。為了讓紋理到達 GPU,必須進行兩個復制:一個是從磁盤到內存,GPU 可以“看到”的內存,然后從 GPU 可以“看到”的內存到實際駐留在 GPU 上的內存。

有些人認為 CPU 和 GPU 的重疊工作是相同的,然而這些工作實際上并不相同。我們上周討論過這個問題。

要將紋理傳輸到 GPU,必須完成兩個必要的拷貝。盡管可以有很多不必要的拷貝,但至少有兩個是必須的。首先,紋理需要從硬盤拷貝到 GPU 可以看到的內存中;然后,它必須從 GPU 可以看到的內存拷貝到 GPU 的實際內存中。

即使使用直接 GPU 映射(Direct GPU Mapping),這兩個拷貝依然是必不可少的。值得一提的是,雖然你可以使用直接 GPU 映射,但這仍然無法避免這兩個拷貝過程。

直接 GPU 映射過程描述

甚至存在所謂的“定向隊列映射”(Directed Queue Mapping)機制,例如可以讓 GPU 直接將數據寫入系統內存,然后網絡卡可以直接從該內存中讀取數據,無需經過中間緩沖區或復制到多個不同的內存映射區域中。這種方式雖然在一定程度上減少了內存移動,但即使使用這樣的方式,數據依然需要先從磁盤進入系統內存,然后再從系統內存傳輸到 GPU,至少需要兩次數據復制。

目前為止,還沒有聽說過 GPU 能直接從磁盤讀取數據的機制。因此,在整個過程中,無論使用哪種方法,至少需要兩次數據的移動才能將紋理加載進 GPU。

在 OpenGL 中,確實存在一些方法可以繞過第一步復制過程。例如,從磁盤加載到內存時,理論上可以避免某些中間緩沖。但當前我們使用 glTexImage2D 加載紋理時,它會將數據從主機內存復制到 GPU 可見的內存區域,也就是 CPU 可訪問且 GPU 可讀取的一塊內存區域,這樣 GPU 才能進一步將其傳輸到自己的專用顯存中。

這部分并不是我們當前討論的核心。大多數人理解的紋理上傳流程其實只是前半段,我們正在關注的并不是從內存復制到 OpenGL 的那一步,而是更深層的后續紋理傳輸機制,即如何有效控制這兩步傳輸以實現真正的 CPU 和 GPU 異步工作。

我們如何告訴驅動程序開始使用卡片的異步內存傳輸能力來傳輸紋理

我們目前面臨的問題是:如何通知驅動程序,利用顯卡具備的異步內存傳輸功能來開始傳輸紋理數據。為此,我們參考了曾經在某顯卡廠商官網上發布的最新建議,那是我們能找到的最權威且最新的信息。

根據那份文檔的說明,實現這種異步紋理傳輸的方法,是創建一個次級的 OpenGL 上下文來處理傳輸操作。目前,尚未發現其他被官方公開認可的方式來告知驅動程序執行異步傳輸,因此我們暫時只能按照這種方式來嘗試。

但實際上我們也處于一種停滯狀態。因為沒有更明確的官方答復,我們也無法進一步確認這種方法是否就是“正確”的,或者是否已經過時。雖然可以嘗試自己做實驗來驗證,但由于無法獲取顯卡驅動的源代碼,我們根本不知道驅動在內部是如何做出傳輸決策的。因此,也就無法有針對性地設計實驗去引導驅動行為。

換句話說,我們對內部機制一無所知,所以也不知道哪些行為會促使驅動執行我們想要的操作。也許方法很簡單,也可能非常復雜——但我們完全無從判斷。在沒有進一步反饋之前,這件事就只能擱置在這里。

決定等待 NVIDIA 對他們認為最佳紋理傳輸方式的回應

目前,紋理傳輸的相關工作基本暫時擱置。我們的計劃是,暫時觀望,等待顯卡廠商是否會回復,并提供更明確的指導意見,說明他們認為傳輸紋理的最佳實踐是什么。

一旦收到他們的反饋,我們就會回過頭來,根據新的建議調整現有的傳輸流程,使其更符合正確、優化的實現方式。因為現階段我們所參考的方法——即他們之前所發布的方式——現在看來并不像是一個真正理想的方案。

總之,雖然之前的建議提供了一種做法,但結合我們目前對情況的理解,這種方式看起來并不合理或者不再適用。因此,在缺乏更準確信息的前提下,我們選擇先暫停修改,靜待進一步的說明,然后再做調整,以保證最終方案既正確又高效。

提醒我們在兩邊都沒有設置下載屏障,不確定這是否會造成問題

在紋理傳輸流程中,有一個步驟我們目前并未執行,而這是他們最初建議中提到的:在傳輸的兩端加上同步屏障(fence)。我們之所以沒有加,是因為我們并不關心紋理下載是否有明確的同步完成信號。

但也不排除一種可能:也許如果沒有設置同步屏障,驅動程序根本不會意識到需要執行該傳輸操作,導致操作被忽略或執行不當。現在我們對此無法確定,只能等待進一步的信息。

基于這種不確定性,目前我們決定采用觀望態度,不再繼續對這個傳輸流程進行嘗試性修改。因為我們認為,如果繼續投入時間在這方面摸索,很可能不會帶來實質性成果,而且也不是一個高效的時間利用方式。

此外,如果我們真的想深入解決,也可以自己花很多時間做試驗、測試各種行為,但由于我們不了解驅動內部的具體實現,也無法預判哪些操作可能有效,這種嘗試帶來的回報極其有限。因此,現階段的策略就是暫停處理,等待更明確的技術說明,再決定是否調整做法。

替代方案——我們可以繼續在單線程中下載紋理

我們可以繼續在單線程上進行紋理下載,這其實并不會帶來什么嚴重后果,也不會對整體造成致命影響。我們原本想做的是確保GPU和CPU工作的重疊能夠被正確設置起來。如果最后發現無法實現這種重疊優化,那也沒關系。

如果需要的話,我們還有其他方式可以優化,比如通過使用緩沖對象或像素緩沖區來消除第一次復制的開銷。這部分優化流程相對比較清晰,未來如果想要進一步提升性能,是可以繼續深入處理的。

目前,我們希望能夠盡快收到關于最佳傳輸方法的回復,這樣就能根據官方推薦的方式來構建代碼結構,從而在未來優化紋理下載時可以更加順利,打好基礎。但在收到回復之前,決定暫時不再繼續深挖紋理傳輸的問題。

至于接下來的計劃,當時查看了待辦事項文件,雖然一度擔心文件沒有保存,但最后確認其實是保存下來了。因此目前的待辦清單是完整的。

現在有幾個不同的方向可以繼續推進,例如繼續完善渲染方面的工作,修復調試代碼,或者處理一部分音頻相關的調試內容。但正如之前所提到的,目前還不太確定下一步最有趣、最有價值的事情是什么,需要根據具體情況來判斷。可能是整理和完善一些GPU端的代碼,也可能是繼續推進渲染系統的完善。

決定修復調試代碼

我們決定接下來要優先完成調試代碼部分,因為目前這塊代碼寫得比較混亂,處理起來也有些令人不舒服。希望能夠花點時間徹底清理一遍,讓它變得更加整潔,并且確保能穩定運行我們真正需要的調試功能。

具體來說,我們希望建立一個可以可靠使用的性能分析系統(Profiler),以及一些用于控制變量的機制,例如能更方便地在運行時查看和調整變量。除此之外,也希望能夠更系統、更連貫地導出或顯示各種數據。目前在這方面的功能比較薄弱,缺少足夠的工具去直觀地檢查或繪制程序狀態,因此需要增加這類能力。

總體目標是讓調試界面更加實用,能夠支持更深入的問題排查與分析,為后續開發工作打好基礎。之后簡單地切回了項目工程,準備開始動手處理,并先進行了一次編譯,確保代碼是可以正常工作的。

提醒在 Casey 使用的 AMD 機器上,紋理下載問題似乎沒有發生

在我們當前使用的這臺機器上,由于其是 AMD 架構,之前在討論中提到的紋理下載相關的問題實際上并沒有出現。換句話說,這類問題在這臺設備上并不存在,因此在當前環境中是看不到那些錯誤現象的。

不過必須說明,確實有用戶報告過類似的問題,也就是說這些問題是真實存在的,只是在這臺機器上不會復現而已。雖然在當前環境中無法直接看到問題的表現,但問題的存在是被多個用戶證實的,因此需要認真對待。

此外,還有人預測了一些可能的原因和影響,也就是說大家對于該問題背后的機制其實并不完全清楚,只能依賴經驗和推測來判斷可能性。我們也因此更加傾向于等待官方的明確回復,而不是在不了解底層機制的前提下盲目嘗試。

glFlush() 似乎強制下載在單獨的線程上進行

之前遇到紋理下載問題的用戶表示,如果在下載紋理后手動調用一次 glFlush,可以迫使紋理下載在一個獨立的線程中進行,從而成功規避問題。換句話說,通過強制同步,可以讓紋理的異步傳輸過程變得明確,這樣可以暫時解決某些 GPU 上存在的問題。

目前我們也采用了類似的做法,在相應位置添加了 glFlush,雖然我們并不真正依賴它,也不認為這是最終方案,但在等待官方反饋的過程中,這個處理方式對使用某些顯卡的用戶是有幫助的。因此,暫時保留這段代碼是合理的,可以避免他們遇到麻煩,提升程序在更多設備上的兼容性。

我們當前對這段代碼并不特別關心,因為最終會根據官方提供的最佳實踐做出修改,現在更像是一個臨時應對手段,確保程序在不同平臺上的基本運行無誤。

接下來,我們之前的重點已經回到了調試代碼部分。調試系統目前相對混亂,因此打算花點時間來清理,使其能穩定運行我們真正關心的功能,比如:

  • 提供穩定的性能分析器(Profiler),可以可靠地收集運行時數據;
  • 增加對變量的控制能力,方便我們調試和觀察特定狀態;
  • 改善數據的可視化方式,使信息更易于理解與分析。

當前版本中我們缺乏這類手段,很難直接查看運行時的內部數據或狀態變化。我們希望通過完善調試工具,提升對程序行為的洞察能力。

現在打算回到相關代碼中檢查當前狀態,首先進行一次編譯,確保基礎功能正常,再進入下一步的清理與改進工作。
之前做過了
在這里插入圖片描述

我的就是必須flush才行

提醒原始調試代碼的目的

我們之前花了不少時間研究調試系統,希望讓它具備良好的可記錄性和易用性,目標是構建一個既強大又方便的調試工具。不過,最終并沒有完全達到“全面完成”的狀態。這其中一個原因可能是我們嘗試做得太多,加入了各種功能和實驗性設計,導致整個系統變得過于復雜。

經過這些嘗試后我們意識到,也許需要調整策略,從“它能做什么”回歸到“我們真正需要它做什么”。也就是說,為了盡快完成這個調試系統,應該適當收斂功能范圍,避免它變成一個龐大的項目。如果決定將調試系統打造成一個高度可復用、功能強大的通用框架,那固然是一個值得投資的方向。但如果目標僅僅是在當前項目中獲得實用的調試支持,我們更可能需要聚焦于我們最迫切需要的那一部分功能。

換句話說,與其追求一個盡可能全面的系統,不如先構建一個輕量、高效、符合實際需求的調試工具,讓我們可以迅速回到其他開發任務上。這個“收縮”策略更務實,也能避免因調試系統而拖延整個項目進度。未來如果有更多時間和資源,再回頭擴展這個系統也不遲。

專注于調試接口

我們現在首先要做的是查看調試接口部分的內容,因為這是我們上次停下來的地方。現在我們進入的是一個“完善和收尾”的階段,我們打算從幾個不同的角度來處理這個系統,逐個明確我們希望實現的功能,并確保它們能夠良好地支持我們的需求。

比如我們有一個具體的例子:某些函數如 GlobalPauseGlobalUseSoftwareRendering,目前并沒有辦法在運行時動態地去修改這些變量。雖然游戲主體部分我們已經支持了代碼的熱更新,但平臺層并不支持這種機制,所以這些變量目前是無法實時更改的。我們需要解決這個問題。

為了解決這個問題,我們希望能讓這些變量變得易于編輯。目標是能在調試界面中看到這些變量,并以圖形界面的方式對其進行交互操作,比如打勾或輸入值。這樣我們在調試時就能實時控制程序行為,而不必每次都重啟或者重新編譯。

此外,我們注意到目前程序啟動時在初始化 OpenGL 時耗時非常嚴重。特別是在創建 OpenGL 上下文的時候,啟動速度變得很慢,這對開發調試帶來很大不便。有時我們甚至寧愿使用軟件渲染來避免這部分性能開銷。

因此,當前的重點在于:

  1. 為平臺層關鍵變量提供運行時調試控制界面;
  2. 清理和簡化調試接口代碼,使其只保留我們真正需要的功能;
  3. 思考是否需要臨時回退到軟件渲染以加快調試時的開發效率;
  4. 優化調試系統的使用體驗,確保我們能夠靈活而快速地操作調試信息。

總之,我們希望通過一次全面的、目標明確的梳理,把調試工具從一個“原型”提升到一個更穩定、實用的階段。

win32_game.cpp 中 GlobalPause 和 GlobalUseSoftwareRendering 很適合包含在接口中

在這里,我們希望能有一個簡單的方式來控制一些全局變量,比如 GlobalPauseGlobalUseSoftwareRendering,這些是希望能夠在調試過程中方便地修改的變量。現在的目標是找到一種方法,通過合適的界面讓這些變量可以被編輯和控制。

最理想的情況是,如果我們處于“元編程”的思維模式下,可能會選擇直接在代碼中標記這些變量,使它們能夠被自動識別并進行修改。這種方式成本幾乎為零,可以讓這些變量直接暴露給調試工具。但是,這種方式可能會讓代碼變得過于復雜,所以我們希望保持簡單,不引入過多的外部復雜性。

接下來的最佳選擇是,在代碼中找到一個合適的地方聲明這些變量,讓它們可以被編輯。在實際操作時,可以將 GlobalPauseGlobalUseSoftwareRendering 放到一個特定的位置,這樣就可以通過調試工具將它們設為可編輯。

例如,在程序的主循環部分(如 while running 這一塊)中,我們可以聲明這些變量,并通過一個調試工具接口將它們暴露出來。我們已經有一種方法可以通過 debug_var 函數將變量暴露出來,在 debug_var 中定義變量類型并將其注冊為調試變量。

具體的操作方法是,通過 debug_var 函數將這些變量聲明為“可編輯的變量”,這樣就能在調試過程中實時修改這些變量的值。不過,目前這種方法是通過全局常量的方式來實現的,而這種做法的缺點是可能導致一些不必要的復雜性。因此,是否繼續采用這種方式還需要進一步考慮。

總之,當前的目標是使得 GlobalPauseGlobalUseSoftwareRendering 這類關鍵變量能夠通過調試接口被實時控制,以便在調試過程中更加靈活地管理程序行為,同時確保實現方法足夠簡單和高效。

win32_game.cpp - DEBUG_EDIT() 用于包含 GlobalPause 和 GlobalUseSoftwareRendering

如前所述,這個問題可能是我們走得有些太遠了。現在的目標是簡化一些功能,找到一種更直接的方式來處理調試變量,避免過于復雜的實現。在這種情況下,可能我們只需要一個簡潔的方式來聲明哪些變量是可調試的,而不是追求過于復雜的方案。

理想的做法是,我們可以通過簡單的標記來指出某個變量是“可調試的”。這種方式可以讓我們快速指定需要調試的變量,同時避免引入過多的復雜性。我們可以像之前那樣,使用字符串路徑來組織這些變量,以確保界面不會被過多的變量搞得凌亂。通過這種方式,調試的變量可以有分類,避免界面被過多變量占據,影響操作的流暢性。

比如說,在這次的實現中,我們可以將 GlobalPauseGlobalUseSoftwareRendering 這些變量放在合適的路徑下,像是 platform/pausedplatform/soft_rendering 這樣的路徑結構。這樣可以保持調試界面的整潔,并且也不需要做太復雜的操作,只需要明確地指示哪些變量是需要編輯的就行了。

總的來說,這種方法是通過在代碼中簡潔地定義調試變量,給它們指定一個路徑,使得它們在調試過程中可以輕松被編輯。這種方式能有效地簡化操作,同時保持調試功能的完整性。

GlobalUseSoftwareRendering 貌似之前已經改成DEBUG_IF

在這里插入圖片描述

直接添加一個吧

在這里插入圖片描述

在這里插入圖片描述

對于使用 game_config.h 來指定調試變量的方式表示懷疑

目前的想法是,我們可能需要回到之前的做法,通過配置文件來管理調試變量。然而,使用配置文件的方式讓我感覺有些不太合適,因為它導致了一個問題,就是配置文件變得非常龐大,而且每次添加新的變量時,都需要手動去更新配置文件。這種方式雖然可以把所有設置集中在一個文件中,方便抓取和管理,但也讓流程變得有些繁瑣和復雜。

我并不確定這是一個壞方法,可能這是某些情況下確實有效的做法,但我個人感覺它有些過于復雜。如果我們繼續這樣做,可能會面臨更多的麻煩,比如每次修改或添加代碼時,都得再去更新這個配置文件,這給維護帶來了很多額外的負擔。

另外,盡管這樣做可以更容易地管理一整套設置,但是過于依賴配置文件可能會造成不必要的復雜性。我傾向于希望能簡化這個過程,減少復雜的配置和管理。或許可以考慮將調試變量直接綁定到調試系統中的某些編輯點,這樣每次就只需要關注必要的變量,而不需要每次都去更新配置文件。

因此,我的想法是將調試變量的綁定處理方式簡化一點,減少對配置文件的依賴,讓開發和維護變得更加直接和高效。這樣做雖然不一定比以前的方式更高效,但至少能讓過程變得更簡單,避免無謂的復雜性。這就是我現在的考慮,目標是減少復雜性,讓系統盡可能簡單明了。

win32_game.cpp - 用 DEBUG_VALUE() 替換 DEBUG_EDIT() 并檢查是否有效

我們正在考慮簡化調試變量的管理,可能通過使用debug value這一方式來替代以前的debug variable概念。我們希望能夠簡化系統,將變量管理方式精簡成一種更直接、易于維護的方式。原本我們是通過標記變量來讓它們可編輯,或者通過配置文件來管理它們,但這些方法似乎有些復雜且冗余,因此決定嘗試一種更簡單的方案。

當前的方案是我們之前使用的“數據塊”機制,其中包含了debug id和一些關聯的標識符。通過給每個數據塊分配一個唯一的ID,系統能夠穩定地跟蹤和識別這些變量。這個ID其實并不復雜,我們可以用任意穩定的指針來作為標識符,這樣就能確保每個調試變量都有唯一的標識。通過這種方式,系統能夠在不增加過多復雜性的情況下,繼續管理和識別這些變量。

這種方法的好處在于,它不需要我們去維護龐大的配置文件,也不需要為每個變量單獨設置調試選項。只要為每個變量分配一個唯一的標識符,系統就能自動處理其他部分。這種方式簡單直觀,并且沒有額外的負擔。

接下來,我們打算進一步測試這種方法的可行性,看看它是否能更有效地管理調試變量,同時避免過多的復雜配置。如果這種方法行得通,它將大大簡化調試過程,提高開發效率。
在這里插入圖片描述

修改一下錯誤
在這里插入圖片描述

未處理的結束數據塊

當前面臨的問題是調試過程中的時間延遲非常長,令人非常不滿。經過檢查后,發現原本應該通過“開始數據塊”和“結束數據塊”來捕捉調試信息,但似乎出現了一些問題,導致無法按預期工作。懷疑可能是由于調試代碼沒有完全完成,造成了一些錯誤或不一致。

在這個過程中,應該按照正確的流程使用“開始數據塊”和“結束數據塊”來包裹調試信息,這是最合適的做法。雖然一開始的檢查看起來沒有什么問題,但還是有可能是由于調試代碼的不完善,導致它無法正常工作。因此,接下來需要仔細檢查代碼,弄清楚是哪里出了問題,特別是看看是否在調試過程中遺漏了某些關鍵的部分或者配置。
在這里插入圖片描述

調試接口在游戲模式下不可見

目前的調試問題是,盡管已經做了調試設置,但在游戲模式下并沒有看到調試輸出,這讓人感到困擾。原本應該能夠看到調試信息,但目前沒有顯示出來,可能是由于某些調試內容沒有正確打印出來。

考慮到調試文本是白色的,如果它出現在背景上,確實可能被遮擋住,所以有可能看不清。但問題在于,這些調試信息應該被放置在最上層,而沒有出現的原因目前不明確。

在之前的調試過程中,已經把調試相關的調用移到了平臺的某個位置,理論上這些調用應該已經正確添加并顯示。但目前看不到輸出,懷疑是因為某些排序或者圖形層級的問題,可能是調試文本沒有被正確地放置在前景顯示層。

下一步需要深入分析為什么沒有看到調試信息,嘗試找出具體原因并修復它。
在這里插入圖片描述

在這里插入圖片描述

在這里插入圖片描述

檢查是否通過不渲染世界來繪制調試組件

為了調試問題,首先決定驗證調試信息是否真的被渲染出來。方法是通過使游戲世界模式不進行渲染,從而避免它有機會進行任何渲染。這種方法可以簡單有效地檢查調試信息是否顯示。

具體來說,可以修改更新和渲染世界的代碼,特別是渲染組部分。如果將其設置為零,渲染過程將被阻止,這樣可以確保不會有任何內容被渲染出來,從而不受其他圖形的干擾,可以專注于查看調試信息是否被正確繪制。

嘗試過這種方式后,發現調試信息實際上仍然被渲染出來。為了進一步確認,需要在渲染之前添加一個清除操作,以確保沒有其他圖形影響到調試輸出的顯示。
在這里插入圖片描述

在這里插入圖片描述

在這里插入圖片描述

在這里插入圖片描述

在繪制之前添加清除操作

可以稍微調整一下,將“if zero”語句向下移動一點。這樣做的目的是確保屏幕在渲染之前被清空。這樣就可以檢查是否仍然能看到調試信息,從而判斷問題是否出在排序上。通過這種方式,可以確認調試信息是否因為排序問題未能正確顯示。

確認調試接口在游戲中沒有顯示,因為存在排序問題

如我所料,問題出在這些元素的排序上,它們被排序到低于游戲世界中的內容。因此,需要一種方法確保這些調試信息始終排在最前面,不能被游戲中的其他內容遮擋。問題的關鍵在于如何有效地做到這一點。

對于排序鍵(sort key),實際上我們有很大的控制權,因為它是我們自定義的。因此,我可以做的是在執行調試文本輸出時,確保這些文本的排序值(z值)被強制設置為更高的優先級。具體來說,在推送位圖(push bitmap)時,可以加入一種機制,覆蓋默認的z值,從而確保調試文本始終排在前面,避免被游戲世界的其他內容遮擋。

game_render_group.cpp - 向 PushBitmap 添加 SortBias -> Dim.Basis.SortKey

在推送位圖(push bitmap)例程中,可以看到它會遍歷并推送渲染元素,而這些元素的排序是通過GetUsedBitmapDim調用返回的結果來確定的。這些結果會決定渲染元素的排序方式。但是,實際上可以自由傳遞其他信息來修改排序行為。

一種方法是可以在排序過程中添加一個偏置值。通過這種方式,可以在排序時對元素進行額外的調整,確保它們按預期的順序排列。例如,可以在排序鍵(sort key)中加入一個額外的偏置值,這樣做默認情況下不會影響排序,但如果需要的話,可以根據需求調整排序順序。由于目前排序鍵是浮點數類型,所以可以在此基礎上進行調整,以便在執行推送位圖時確保它們按預期的方式排序。

這種方式實現起來非常簡單,只需在推送位圖時傳入一個調整后的排序值,就能輕松控制渲染順序。
在這里插入圖片描述

在這里插入圖片描述

在這里插入圖片描述

game_debug.cpp - 為 PushBitmap 調用添加 FLT_MAX 排序偏差,以便我們能夠獲得最靠近我們的調試界面(在 Z 軸上)

為了確保調試信息總是顯示在最前面,可以在 push bitmap 調用中加入一個排序偏移量。具體而言,可以在調用的末尾添加一個較大的排序偏差值,使得調試信息始終位于渲染的最前面。

在坐標系統中,我們希望排序值接近我們,即 z 值朝向我們。因此,應該設置一個相對較大的數字作為排序偏差。雖然可以將其設置為最大值,但不一定需要這么做,因為只要確保值足夠大,就能保證調試信息顯示在前面。使用最大值雖然可以確保偏差的效果,但也需要考慮是否會對浮動點值產生影響。一般來說,如果值過大,系統應該會自動對其進行限制,因此設置一個合理的大值就可以了。

設置了排序偏差后,調試信息回到了屏幕上,但也可以看到它們顯示不太清晰。這是因為如果背景色是亮白色,白色的文本會和背景融為一體,導致無法清楚看到調試信息。因此,需要考慮調整文本顏色或背景顏色,使調試信息更為明顯。
在這里插入圖片描述

在這里插入圖片描述

這個函數很煩申明的太多

在這里插入圖片描述

在這里插入圖片描述

在這里插入圖片描述

在這里插入圖片描述

game_debug.cpp - 通過復制 PushBitmap 調用、稍微移動位置并改變顏色來為文本添加陰影

為了讓調試信息在各種背景下都能清晰可見,可以為調試文本添加簡單的陰影效果。具體方法是,在繪制文字時,先繪制一次稍微偏移且為黑色的文本作為陰影,然后再繪制一次正常顏色(如白色)的文本。

具體實現步驟如下:

  • push bitmap 的調用中,進行兩次繪制。第一次繪制時,給位圖偏移一個小量(例如在 Y 方向下移一點),并設置繪制顏色為黑色。這樣生成一個簡單的陰影效果。
  • 第二次繪制正常的文字,即位置不偏移,顏色為白色,這樣確保文字本身覆蓋在陰影之上。

這種方法雖然簡單,但足以保證調試文本即使在非常亮或者復雜的背景上依然清晰可見。雖然視覺效果并不是特別精致,但作為調試用途已經足夠實用。

另外,因為現在可以自定義排序鍵(sort key),可以通過設定不同的排序值來確保繪制順序正確。也就是說,先繪制黑色陰影,再繪制白色文本,確保文本總是在陰影之上顯示。具體操作是在兩次 push bitmap 調用中分別指定略微不同的排序偏移值,保證陰影先繪制、文本后繪制。

這種處理方式目前已經能很好滿足調試顯示的需要,以后如果需要更漂亮的調試輸出,還可以進一步改進。現在這樣已經算是非常不錯的階段性成果。
在這里插入圖片描述

為了鼠標能夠窗口坐標對應上Win32ResizeDIBSection 必須改為點全屏的大小

在這里插入圖片描述

我電腦屏幕 1707 x 960

在這里插入圖片描述

在這里插入圖片描述

窗口改變坐標就會對不上

在這里插入圖片描述

在這里插入圖片描述

回到之前的任務 - 檢查交互選擇是否有效,并確認未處理的結束數據塊不是由添加 GlobalPause 和 GlobalUseSoftwareRendering 引入的 bug

我們最初的目標是希望能夠編輯這些數值,因此需要回到原本的工作方向上來。目前對于這些“blocks”的具體作用還不是很清楚,同時也不太了解在這個“surface”上的選擇功能是否仍然正常運行。經過檢查,確認了“surface”的選擇功能依然是可以使用的。

進一步測試后發現,之前擔心的在數據塊(data blocks)中的處理并沒有問題。數據顯示方面,之前出現的打印信息實際上只是之前在插入數據時犯的一些低級錯誤導致的,而不是當前邏輯上的錯誤。至于“begin”和“end”的處理,也確認在新的情況下沒有問題,一切運行正常。

當前整體系統的狀態良好,之前的功能仍然保持正常,基本沒有出現破壞性的問題。因此,下一步的重點是讓這些數值變得可以編輯,只要完成這一點,整體功能就能夠達到預期,項目就能夠順利推進。
在這里插入圖片描述

檢查調試打印層級

目前還有一個地方不太清楚,就是對于當前這種readout(讀出)的顯示情況,具體是怎么導致的。感覺現在的readout似乎應該歸屬于某個更明確的上層結構之下,而不是像現在這樣獨立存在。因此懷疑是縮進(indentation)處理得不正確,導致了結構上出現了問題。

為了確認具體原因,在正式開始其他工作之前,決定先快速查看一下相關代碼,弄清楚readout顯示結構不正確的問題是怎么發生的。目的是在繼續開發之前,確保整個結構和邏輯都是清晰且合理的。

win32_game.cpp - 檢查“Platform”應該如何在 DEBUG_BEGIN_DATA_BLOCK() 中工作

這里重新回到我們的debug區域,查看debug的起點和終點。當前情況下,涉及到了platform(平臺)部分的內容。根據現有的觀察,可以推測在這個流程中,platform的處理可能和之前關注的readout結構問題有所關聯。

目前推測,在debug的過程中,platform相關的數據或者邏輯可能插入到了不合適的位置,進一步導致了縮進不正確或結構混亂的現象。這也是導致readout沒有正確歸屬到應有層級下的潛在原因之一。

因此,在接下來的步驟中,需要更仔細地檢查platform部分的代碼邏輯,以及debug區域的整體布局,確保縮進、結構、邏輯層級都符合預期,避免因結構混亂導致其他后續問題。當前主要目標依然是理順現有結構,為后續的編輯功能改進打下穩定的基礎。

game_world_mode.cpp - Simulation_Entity | 確認打印塊名稱尚未正確實現

目前對于現有的實現方式還不太確定,因此決定再次回頭查看一下world move部分,特別是它在執行debug值輸出時的具體做法。觀察后發現,主要處理的內容似乎是simulation_entity,這是對應的數據塊名稱。但具體來說,像platform control這一類內容似乎并沒有明確的對應或者處理邏輯,也可能是因為當時還沒有真正完成打印實際塊名稱的功能。

推測當時的打印機制本身就比較隨意且混亂,并沒有經過精細設計,因此輸出結果看起來零散且不統一。進一步確認后,基本可以認定目前這部分代碼屬于未完成或者半成品狀態,需要回頭進行整理和完善。

在跳回游戲運行環境再次觀察后,確認了之前的推測:當前的打印邏輯是簡單輸出塊名稱(比如simulation entity),然后遇到結束標記時,因為沒有正確處理,所以出現了未處理的情況。說明整體打印系統確實不完善,導致了這些混亂的輸出。

因此,接下來的計劃是,著手清理這一塊的代碼。通過完善打印輸出邏輯,使之能夠統一、清晰地處理所有數據塊,而不是依賴于零散的、特例化的if判斷。只要把這部分處理完善了,不但可以提升整體系統的可維護性,還可以順帶去掉之前一些臨時加上的冗余控制邏輯,從而讓代碼更簡潔、更穩定。

總結來說,當前主要的目標是系統性地清理、統一數據塊打印處理邏輯,規范化輸出格式,為后續開發打下更堅實的基礎。

game_debug_interface.h - 通過移除 DEBUG_IF 和 DEBUG_VARIABLE 來簡化復雜性

當前聚焦在scene部分,尤其是debug接口相關的代碼上。在查看這部分的實現時,明確了一件事,就是之前的debug if判斷和debug variable相關的機制,打算徹底放棄不再使用。那一套設計原本只是一個實驗性質的嘗試,但隨著項目推進,發現它引入了過多不必要的復雜性,反而使整體流程變得混亂。

為了簡化系統,決定不再維持那種需要維護.h文件(頭文件)并且分開處理的多套系統。現階段已經非常明確了需求:只需要能做簡單直接的數據塊(data dump)輸出即可,因此沒必要繼續保留額外的一套debug控制體系。

因此,計劃就是直接把這些老的debug接口代碼徹底刪除,完全清除掉原本用來支持這種復雜debug機制的部分。然后再思考如果沒有這些東西,要怎樣重新組織代碼。目標是將所有需要輸出的數據,都統一走新設計的簡潔的數據塊輸出路徑,而不再依賴那種冗余且難以維護的老方法。

總結下來,就是專注于降低系統復雜性,移除冗余系統,只保留一套簡單清晰的數據輸出機制,為后續開發打下更加高效、可控的基礎。當前具體行動就是徹底刪除舊debug接口代碼,并著手替換成統一的簡單邏輯。
在這里插入圖片描述

將 DEBUG_VARIABLE 和 DEBUG_IF 的調用移到全局變量

我們需要做一些額外的工作,把當前的內容提取出來,整理成一組全局變量。現有的系統雖然已經能夠運行,但它并不是最完美或終極的版本,不是所有需求都能完全覆蓋。為了控制調試系統的復雜度,決定不繼續增加更多復雜的結構,因為原本系統的復雜度已經有些超出了想投入的時間和精力。

為了讓功能繼續運作,需要把相關的數據整理成可以被訪問的全局變量。這些變量需要在某處被統一聲明,并且在需要時可以方便地訪問。最簡單的方法是,將現有使用到的變量收集起來,集中放置到某個統一的位置,比如放到一個像 game.h 這樣的頭文件中。只需要在那里進行統一的聲明即可,這樣的處理方式已經能滿足當前的需求。

目前已經在某些地方初步有類似的整理趨勢了,可以在現有基礎上繼續沿用這種方式,把需要的變量放進公共區域。這樣既能保持系統的簡潔,又方便后續的維護和擴展。接下來會具體看看這些變量應該放置在哪里。

game_config.h - 將 #define 更改為 global_variable

可以直接把 game_config 里面的內容拿出來,把其中的變量作為全局變量來處理。其實也可以選擇不去特意刪除原來的,只需要在新的位置,比如配置部分,把這些變量重新聲明成全局的就可以了。可以直接簡單粗暴地把它們設置為全局變量,并且公開它們,這樣它們可以直接被訪問和修改。最初起這樣的想法時也沒覺得有多大問題,所以就這么處理就行。

對于這些變量的初始賦值,可以直接設為它們原本的默認值,沒有必要做太復雜或多余的處理。不需要保持原來那么冗長和詳細的命名方式,因為之前的命名之所以那么長,是為了路徑嵌套在變量名里,方便定位。但現在這些變量作為全局變量存在后,就不再需要那樣詳細地嵌套命名了,可以更簡潔一些。整體上,既能保持功能完整,又能讓代碼變得更加清爽簡潔。
在這里插入圖片描述

game_render_group.cpp - 檢查是否沒有代碼寫入 game_config.h

還需要做的一件重要的事情是,移除之前用來向 game_config 寫入數據的相關代碼。因為現在已經不希望再去覆蓋 game_config,所以必須確保這些寫操作已經被去掉。檢查了一下,確認之前的寫入邏輯已經被刪除了,這樣就不會意外地改動配置內容,從而導致不可預期的問題。

主要是要確保舊的寫入邏輯不會繼續執行,否則有可能破壞現有的數據,帶來很大的麻煩。在確認沒有殘留寫操作之后,接下來可以繼續處理后續的整理和調整工作。接下來會進一步查看當前代碼狀態,確保整體邏輯干凈、正確,方便繼續推進。
在這里插入圖片描述

檢查 game_config.h 的包含位置 [在 game_platform.h 中] 并將包含移至 game.h

需要確認一下 game_config 實際上是在哪里被使用的。查看了一下,發現它應該是在 game.h 文件的最頂端被包含進來的,甚至是在定義任何其他類型之前就已經引入了。這種做法顯然不太理想,因為希望能夠在引入配置之前,先定義好一些必要的類型,方便后續使用。

因此,應該將 game_config 的包含位置往后挪一挪,放到更合適的地方,而不是一開始就引入。同時,考慮到模塊結構的清晰性,更傾向于將它放到 game.h 文件內部,而不是繼續放在平臺相關的部分。平臺代碼應該獨立管理自己的內容,不需要依賴或者插手游戲邏輯這邊的配置。

簡單來說,就是需要調整包含順序,避免過早引入配置頭文件,保持類型定義和模塊結構的清晰、合理,確保各部分職責分明,不互相干擾。接下來會繼續細化調整,把整體組織得更加合理。
這個我直接在game_config.h 引入頭文件了應該不影響

修正 game_config.h 中全局變量的類型和初始化器,并在其余代碼中更改 DEBUG_IF 和 DEBUG_VARIABLE 調用,一一消除編譯錯誤,直到…

首先整理了變量的定義,現在所有需要的變量都是 real32 類型,并且通過等號直接賦初值。這樣一來,整體結構已經變得比較清晰,不再需要像以前那樣復雜地進行調試相關的宏定義判斷。原本基于 DEBUG_IF 這種條件編譯的邏輯也可以去掉,直接用簡單的 if 判斷就可以了,進一步簡化了代碼。

同時,原來用于單獨處理某些宏定義或條件變量的地方,現在也不再需要,因為在統一聲明這些全局變量時,相關的初始化過程就會自動發生。不需要再額外寫特殊處理邏輯。

在處理過程中,發現有些地方出現了未定義標識符的錯誤,初步判斷是因為頭文件引用順序的問題。比如在 game.h 還沒包含相關定義時,game_config 里的內容已經開始使用了,導致編譯器找不到對應的類型定義。進一步分析后,確定是之前在 global_constants 這一部分的引用結構有點問題。

決定保留 global 這個命名詞匯,因為當前階段還沒有準備好做更大范圍的改動,所以在命名時依然希望能清晰標識出這些變量是全局常量。最終選擇了在 global_constants 中維護這些變量,并且打算以小規模、逐步推進的方式進行整理,避免一次性改動太大導致額外的問題。

之后對代碼中與調試功能相關的一些地方進行了檢查,發現并沒有真正發生復雜的處理或者有趣的邏輯變化,只是一些簡單的重構和清理工作,屬于比較基礎的代碼維護階段。下一步會繼續檢查編譯器報錯的細節,確認是否還有遺漏的問題。
在這里插入圖片描述

改了全局變量剩余挨著把DEBUG_IF改為if
在這里插入圖片描述

… 到達 Global_Renderer_ShowLightingSamples,它被直接移動到 game_render.cpp

在處理過程中發現 show_lighting_samples 這個變量沒有正確生效。檢查后發現,在 game_render.cpprender.cpp 這樣的文件中,并沒有包含配置相關的頭文件。初步推測原因是因為這些文件屬于平臺層,而在平臺層建立兩層架構系統后,這部分代碼并沒有自動包含配置頭文件,這是可以接受的情況。

既然如此,就決定不強行讓這些文件包含統一的配置文件,而是把相關的變量直接分散到各自對應的文件中。比如,show_lighting_samples 只與渲染相關,因此就直接在渲染相關的文件中定義和使用,不再依賴統一的配置頭文件。這種處理方式符合模塊化的思路,各個文件只負責自己的邏輯,不用依賴不必要的全局內容。

未來可能會徹底移除 game_config.h,把其中的配置變量根據實際用途分別移到合適的源文件或者模塊中去。這樣可以讓項目結構更加清晰,避免無關模塊之間互相引用,減少不必要的耦合關系。

當前階段,為了快速調整和過渡,只需要把需要的變量移動到正確的文件中,保證它們在各自作用域內能夠正常訪問和使用即可。之后如果有需要,可以再進一步優化和重構。

win32_game.cpp - 移動(并之后刪除)Global_Renderer_UseSoftware 從 game_config.h,并創建一個枚舉來表示渲染類型,以便更好地管理 Win32DisplayBufferInWindow 的情況。

首先處理了一個全局變量 global_render_use_software,這個變量原本是用于控制渲染方式的,屬于平臺層的內容,因此決定將其從公共配置中移除,直接放到窗口或渲染相關的文件中,讓它歸屬于正確的模塊。這樣做可以使各模塊的職責更加清晰。

在整理的過程中,發現之前對變量的命名也不夠準確和清晰。實際上需要區分兩個不同的概念:一個是渲染是否使用軟件渲染,另一個是顯示時是否使用 OpenGL。這兩者本質上是獨立的狀態組合。為了更合理地管理這些狀態,重新設計了一個新的枚舉類型 rendering_type,用來清晰地表達渲染和顯示的不同模式。

具體定義了三種渲染模式:

  1. 渲染使用 OpenGL,顯示也使用 OpenGL(最標準的路徑,一切通過 OpenGL 完成)。
  2. 渲染使用軟件,顯示使用 OpenGL(渲染生成圖像后,通過 OpenGL 顯示出來)。
  3. 渲染使用軟件,顯示使用 GDI(完全使用軟件生成圖像并通過傳統的 GDI 接口顯示)。

注意到第四種理論組合(渲染用 OpenGL,顯示用 GDI)實際上并不被支持,也不會實現,因此沒有納入處理邏輯。

為了保證邏輯正確,加入了斷言檢查,在運行時確保 global_rendering_type 的值一定在可接受的范圍內。如果出現意外的枚舉值,則斷言失敗,便于及時發現邏輯錯誤。

整理完畢后,不僅去掉了原來命名混亂、易混淆的全局布爾變量,還通過引入明確的枚舉類型,讓渲染模式的判斷邏輯更加簡潔、直觀。現在代碼根據 global_rendering_type 的不同取值來選擇不同的渲染和顯示路徑,整體結構更加清晰,后續擴展和維護也變得更容易。

特別說明,將枚舉的排列順序特意按照使用頻率和邏輯優先級排序,比如把最常用、最標準的 OpenGL 渲染顯示模式放在第一個位置,以便代碼閱讀和理解時更加自然順暢。
在這里插入圖片描述

在這里插入圖片描述

在這里插入圖片描述

win32_game.cpp - 枚舉組件排序的原因是使 0 值成為默認值

為了讓系統初始化更簡單,把枚舉類型的默認值專門設置為 0。這樣所有數據在分配時,只需要簡單地清零(比如 memset 置零)就能完成默認初始化,無需額外處理。這種做法非常理想,因為能夠減少初始化時出錯的概率,也能讓內存清理后的狀態天然就是合法且合理的初始狀態。所以在設計時,特意將最常見、最標準的渲染模式(使用 OpenGL 渲染和顯示)對應到枚舉值 0

完成了渲染類型枚舉的調整后,進行了編譯測試,確認改動能夠順利編譯通過,沒有引入新的編譯錯誤。

在完成了新的全局變量 global_rendering_type 的定義和注冊后,注意到一個后續的小問題:當系統"宣布"(announce)這些全局變量時,global_rendering_type 這種枚舉變量顯示出來的是純數字,而不是人類可讀的枚舉常量名。換句話說,現在看到的只是數值,比如 012,而不是更清楚的標識比如 RenderType_OpenGL, RenderType_Software_DisplayOpenGL 等等。

雖然這種顯示方式能夠正常運行,但可讀性很差,不利于調試和理解。因此考慮在之后進一步優化這部分,在"宣布"全局變量時,可以改進為以符號名而不是數字的形式顯示。這部分計劃留到明天再具體處理。

整體來說,這次改動的要點是保證默認初始化簡單可靠,同時也提前預想到未來在調試可視化時需要提升人類可讀性的問題,為后續細化打下了基礎。
在這里插入圖片描述

奇怪我的顯示怎么是浮點數

DebugType決定的

在這里插入圖片描述

在這里插入圖片描述

在這里插入圖片描述

在這里插入圖片描述

還是不對

在這里插入圖片描述

拷貝宏替換的看看調用情況

在這里插入圖片描述

在這里插入圖片描述

int32嗎

在這里插入圖片描述

在這里插入圖片描述

在這里插入圖片描述

typedef 沒法區分類型

typedef int32_t int32;
typedef int32_t bool32;

定義bool32 應該是為了類型對齊吧

添加一個傳類型的宏

在這里插入圖片描述

在這里插入圖片描述

在這里插入圖片描述

另外一個問題打印block 的內容

在這里插入圖片描述

在這里插入圖片描述

在這里插入圖片描述

明天的計劃:為編輯連接調試輸出,可能進行清理

首先,目標是減少一些不必要的復雜性,使得代碼更簡潔,并且更專注于實現調試系統的可用性。通過刪除一些不必要的部分,減輕了系統的負擔,接下來會集中精力在實現編輯功能上,這樣可以使系統變得更加易用和高效。

計劃是,明天會繼續進行下一步的工作,包括連接編輯功能,并清理當前的代碼。這將讓系統更接近一個可以正常工作的狀態,使其更加穩定和可操作。通過這樣的方式,可以確保調試系統更加簡潔且具備良好的功能。

總體而言,刪除不必要的復雜部分是為了將焦點放在調試系統的功能實現上,這樣做能讓系統更好地應對未來的需求,而不會在不必要的地方浪費過多精力。

問答環節

在什么時候將所有內容分成多個文件是 的,而什么時候是 的?

關于將代碼分割成多個文件,什么時候是好的選擇,什么時候又是壞的選擇,主要取決于具體的情況,特別是對開發者個人的工作習慣和項目的要求。

首先,文件的目的是為了幫助開發者更好地組織代碼。如果一個開發者覺得將所有的代碼放在一個文件里更高效,那么就沒有問題,完全可以這樣做。沒有絕對的標準來要求代碼必須分成多個文件。開發者可以根據自己的需求將代碼按邏輯區域分割成多個文件,方便在編輯器中同時查看和修改。不同的編輯器對代碼的展示和導航方式也不同,所以根據使用的工具和開發習慣來分割代碼文件是合理的。

但是,存在一些特殊的場景,特別是在大型項目中,需要特別考慮編譯效率。在像一些大型引擎(如 Unreal Engine)這種項目中,文件分割非常多,可能導致編譯時間長。每次修改文件時,編譯器需要重新編譯該文件以及它的依賴項,如果這個成本太高,分割文件的策略就需要更加謹慎。因此,在這類環境中,開發者需要非常清楚自己的代碼如何分割,避免影響編譯效率。

此外,開發者還需要關注項目的構建系統。如果構建系統不支持快速的增量編譯,或者每次修改文件都需要重新編譯大量內容,那么文件的劃分就需要考慮如何減少編譯次數。對某些項目來說,分割文件是必要的,因為這直接關系到項目的構建效率。

對于版本控制系統的選擇也有影響。如果使用的是無鎖的版本控制系統(如 Git),多個開發者可以同時編輯文件,合并變更時才會碰到沖突;而使用鎖定的版本控制系統(如 Perforce)時,每次一個人編輯文件時,其他人無法同時編輯,就會限制并發性。在這種情況下,開發者需要更加注意文件的分割策略,因為文件劃分的粒度直接影響團隊協作效率。

總結來說,文件是否分割以及如何分割,完全取決于開發者的工作習慣、使用的工具、以及項目的具體需求。如果沒有特別的構建或版本控制上的約束,分割文件應該是為了提高開發者的工作效率,而不是單純為了遵守某些規范。

增量編譯的實現依賴于對代碼變化的追蹤、有效的依賴管理以及對中間結果的緩存。下面是如何實現增量編譯的基本步驟和思路:

1. 跟蹤代碼變化

增量編譯的核心是能夠識別哪些文件或代碼部分發生了變化。通常,構建工具會使用以下方法來跟蹤變化:

  • 時間戳:通過檢查文件的修改時間,來判斷文件是否發生了變化。
  • 文件哈希:使用哈希算法(例如 MD5 或 SHA)對文件內容進行校驗,以判斷文件內容是否發生了變化。
  • 源代碼管理系統(如 Git):一些增量編譯系統會集成版本控制工具,使用 Git 等系統的變更記錄來判斷文件或代碼段是否發生了變化。

2. 管理依賴關系

增量編譯的另一個關鍵是能夠追蹤文件之間的依賴關系,確保修改過的文件及其所有依賴的文件都會被重新編譯。以下是如何管理依賴關系:

  • 構建文件或依賴圖:通過生成一個文件依賴圖(dependency graph)來標明哪些文件依賴于哪些其他文件。當文件發生變化時,系統可以通過這個圖來計算需要重新編譯的文件。
  • 頭文件依賴:尤其是在 C 和 C++ 項目中,源文件依賴于頭文件。如果頭文件發生變化,所有包含該頭文件的源文件都需要重新編譯。因此,構建系統會自動管理頭文件的變化。

3. 緩存中間結果

為了加速增量編譯,可以緩存編譯過程中的中間結果(例如對象文件 .o),避免重新編譯沒有變化的部分。實現這一點的方式包括:

  • 對象文件緩存:編譯器將生成的中間對象文件(例如 .o 文件)存儲在緩存中,如果該文件沒有改變,就直接復用這些文件。
  • 增量鏈接:在編譯過程中,鏈接步驟也可以采用增量方式,即只重新鏈接修改過的對象文件。

4. 使用構建工具和系統

許多構建工具和編譯器都已經實現了增量編譯。你可以通過合理配置構建系統來實現增量編譯。

常見的構建工具配置增量編譯的方法:
  • Make / CMake

    • Makefile:在 make 中,增量編譯是基于文件依賴關系的。如果文件 A 依賴文件 B,當文件 B 修改時,make 會自動重新編譯文件 A。只需要配置合適的規則和依賴關系即可。
    • CMake:CMake 本身不直接處理增量編譯,但它生成的 Makefile 會自動處理依賴關系和增量編譯。

    例如,在 Makefile 中定義依賴關系:

    file1.o: file1.cpp file1.hg++ -c file1.cpp -o file1.ofile2.o: file2.cpp file2.hg++ -c file2.cpp -o file2.o
    

    這表示 file1.o 依賴于 file1.cppfile1.h,而 file2.o 依賴于 file2.cppfile2.h。如果 file1.cppfile1.h 改動了,file1.o 會重新編譯,而 file2.o 則不會。

  • Ninja
    Ninja 是一個專為增量編譯設計的構建系統,它專注于高效的增量構建,通過增量構建可以大大提高編譯速度。Ninja 通過生成依賴文件來實現增量編譯,支持精確的文件級別依賴追蹤。

  • Visual Studio
    在 Visual Studio 中,增量編譯通常由其內部的 MSBuild 系統處理。MSBuild 會檢測文件的時間戳或哈希值來判斷是否需要重新編譯文件。如果某個源文件修改了,它會重新編譯該源文件,并更新相應的目標文件。

  • Xcode
    Xcode 也內置了增量編譯功能。它通過跟蹤代碼文件的修改時間、依賴關系以及修改的文件來決定哪些需要重新編譯。Xcode 使用了強大的編譯緩存和依賴追蹤系統,確保每次構建時,只編譯修改過的部分。

5. 實現增量編譯的步驟

假設我們要實現一個簡單的增量編譯系統,步驟如下:

  1. 文件哈希檢查:對每個源文件計算文件的哈希值,記錄它們的狀態(是否已經編譯,或者是否需要重新編譯)。
  2. 依賴關系圖:生成一個依賴關系圖,標明每個文件依賴于哪些文件。
  3. 文件修改檢測:每次構建時,通過文件的修改時間或哈希值判斷哪些文件已經修改過。
  4. 重編譯更新的部分:對于修改過的文件,以及依賴于這些文件的其他文件,執行重新編譯。
  5. 生成中間文件并緩存:將編譯的中間結果(如 .o 文件)存儲起來,以便下次復用。

6. 增量編譯的優化

為了進一步提高增量編譯的速度,還可以采取以下優化措施:

  • 并行編譯:同時編譯多個文件,縮短整體的編譯時間。
  • 預編譯頭文件(PCH):將常用的頭文件預編譯成一個固定的中間結果,以減少每次編譯時的開銷。
  • 增量鏈接:只對變更過的目標文件進行鏈接,而不重新鏈接整個程序。

總結

增量編譯的關鍵在于高效的文件變化檢測、精確的依賴關系管理和緩存機制。在使用構建工具(如 Make、CMake、Ninja、Visual Studio 和 Xcode)時,可以充分利用這些工具提供的增量編譯支持,減少編譯時間,提高開發效率。

這個直播中的“紋理下載”指的是“glTexImage2D”嗎?

目前關于紋理下載的代碼結構已經相對正確,雖然沒有做過于激進的優化。紋理下載的操作涉及將紋理數據傳輸到顯卡上,這一過程通過調用 glTexImage2D 來實現。為了優化這一過程,資產后臺線程可以使用 glTexImage2D 提交紋理到顯卡。

然而,在與NVIDIA的合作中發現,直接在后臺線程中調用這一操作在某些情況下并不穩定,因此目前的做法是等待NVIDIA提供最佳的實現方式。此前的想法是,NVIDIA可能更傾向于讓紋理上傳操作在主線程中進行,而不是在后臺線程中進行,但需要等待他們的建議和確認。

目前“紋理下載”這個術語并沒有特別明確的定義,它只是指將紋理數據從內存傳輸到顯卡內存的過程。在此過程中,唯一的操作是通過 glTexImage2D 完成紋理的傳輸。雖然在未來,可能會引入其他優化措施,比如直接寫入顯卡內存,或者進行更高效的紋理傳輸,但現在這只是一個相對基礎的傳輸過程。

紋理下載有時也被稱為紋理上傳,這兩個術語的使用實際上取決于視角的不同。如果從顯卡的角度來看,它是紋理的上傳;如果從CPU的角度來看,它是紋理的下載。因此,“紋理傳輸”是一個更準確的術語,能夠描述這一過程的雙向性。

你曾經使用過 #pragma section(…) 來將內存分組到不同的段,并讀取映射文件,還是一直使用這種元編程方式?

關于進度部分、分組和讀取映射文件的問題,通常沒有使用這些方法,而是更傾向于采用聲明式編程方式。在編程中,并不習慣去做反向讀取操作。一般來說,通常采用的是傳遞信息的方式進行編程,而不是直接讀取文件進行操作。

離題:如果你想擴展熱代碼重載以處理結構體變化,你是不是必須存儲每個結構體的元數據以及每個分配的信息,這樣你才能遍歷數據,調整數據,移動東西并修復指針?

要實現熱代碼重載以支持結構體的變化,確實需要在每個結構體的分配中存儲元數據,包含結構體的布局信息。這樣做的目的是在數據更新時,可以遍歷和調整數據,確保數據的遷移和結構的更新能夠正常進行。然而,這個過程并不像聽起來那樣復雜。其實,主要是保留與結構體布局相關的信息,這部分工作并不困難。因為C語言編譯器的結構相對簡單,所以對于這個過程來說,并不會帶來巨大的技術難題。

盡管這需要一定的工作量,通常可能需要幾天時間,但這并不是一個非常繁瑣或者復雜的任務。總的來說,這項工作是可行且并不超出常規的編程工作量。

你是否需要特別指定 inline 讓函數內聯,還是編譯器在找到合適的情況下自動內聯?

在C++中,inline關鍵字用于提示編譯器將函數內聯。雖然可以通過inline關鍵字顯式標記函數要求內聯,但編譯器并不一定會遵循這個建議。實際上,編譯器會根據自身的判斷決定是否內聯一個函數。即使函數沒有顯式標記為inline,編譯器也可以選擇內聯這個函數。因此,inline只是一個提示,編譯器的決策權是最大的,決定是否內聯的最終判斷由編譯器來做。

inline本質上是一個建議,并不強制,編譯器可以選擇忽視它。而且,有些情況下編譯器可能根本不支持內聯,或者出于性能等原因不選擇內聯某些函數。對于內聯編譯,編譯器會在編譯時做出判斷,通常只有在代碼中涉及到較為復雜的計算或者性能要求較高的函數時,程序員才會比較關心內聯的效果。

對于一些代碼量較小且不涉及復雜計算的函數,編譯器往往會自己決定是否內聯,而程序員一般不需要過于擔心是否內聯。實際上,在很多情況下,編譯器可以根據情況自動優化代碼,內聯的決定通常不需要開發者干預。

因此,inline關鍵字的使用并不是非常嚴格,編譯器通常會根據自身的優化策略做出判斷。如果編譯器認為某個函數不適合內聯,或者內聯會引起性能問題,它可能會選擇不進行內聯。如果內聯的需求非常強烈,也可以手動將函數實現為宏,或者通過其他方式優化代碼。因此,內聯是否生效不需要太多擔憂,程序員可以更多地關注那些對性能影響較大的核心代碼部分,其他部分通常不會有顯著的影響。

你會嘗試就地修復內存,還是將其復制到新的內存區域?

在處理內存映射時,通常需要將數據從一個內存區域復制到另一個新的內存區域,尤其是當數據結構的大小發生變化時。這是因為當數據結構變得更大時,無法直接在原地修改原有內存空間。原因在于,數據結構變大后,后面的數據也會隨之發生變化,如果繼續在原來的內存區域中修改,就可能覆蓋原本應該保留的數據。因此,在這種情況下,必須創建一個新的內存區域,并將原數據從舊的區域復制到新的區域。

這種做法的關鍵點在于,無法直接在原有的內存區域進行修改,必須為數據的變更分配新的內存空間,這樣可以確保數據結構變更時不會破壞原有數據的完整性。通過這種方式,數據的擴展或變化可以安全地進行,而不會導致內存訪問錯誤或數據丟失。

因此,內存映射通常需要為新數據分配一個新的內存區域,并將數據從舊區域復制到新區域。這種方法相對可靠,也避免了直接修改原內存區域所帶來的潛在風險。

編程風格 - 為什么你把函數的 return 放在前一行?只是為了讓函數名在第一列嗎?

這種編程風格,尤其是函數的返回類型和函數名分開寫,源于個人的習慣。在早期編程時,尤其是使用C++的模板時,由于模板的類型參數通常很長,導致函數聲明或定義經常需要換行,這樣可以避免一行過長。隨著模板的使用逐漸減少,但這種寫法的習慣依然保留下來。

具體來說,習慣性的做法是在函數返回類型的下一行開始寫函數名,這樣的布局使得函數的結構更加清晰。尤其是在函數定義較長或者包含復雜模板類型時,分行能讓代碼更易于閱讀和理解,尤其是在早期模板編程中,模板的類型參數很容易導致一行代碼過長,不得不換行。

雖然現在不再使用模板了,但這種寫法的習慣依然被保留下來。對于個人來說,這種寫法讓代碼看起來更有結構,也符合他們對代碼布局的偏好。這也體現了編程中很多細節和習慣的形成,往往和過去的使用場景和編程經驗密切相關。

是的,我也不知道發生了什么,當我昨晚問關于內聯的問題時,它在所有地方爆炸了。

在編程中,對于函數是否內聯(inline),通常并不會過于關注或焦慮。內聯函數的使用更多的是為了提醒自己,或者作為一種代碼優化的提示。內聯的標記可能是出于以下幾種原因:

  1. 優化目的:有時候標記為內聯,是因為認為這個函數可能只會被調用一次,或者在調用時需要進行優化處理。比如,將多個小函數合并,以便提高效率,減少調用開銷。通過將函數內聯,可以讓編譯器把它直接插入到調用位置,從而可能帶來優化效果。

  2. 函數調用優化:對于一些很短的函數,直接內聯可能會讓編譯器有機會優化代碼。如果某個函數被標記為內聯,編譯器可以根據上下文決定是否將其插入到調用點,從而減少函數調用的開銷。

然而,內聯是否真的有效并不是一種簡單的假設,尤其是在進行性能優化時。今天的編程環境中,并沒有辦法僅憑直覺判斷是否應該內聯某個函數。即使標記為內聯,也不能保證編譯器一定會這么做。最終的性能影響通常需要通過實際的性能測試來衡量。

因此,大多數情況下,內聯標記只是作為一種代碼風格和優化的提示,而不會對程序的最終性能產生直接影響。如果性能成為瓶頸,通常需要通過具體的測試來決定是否進行內聯,而不是依賴單純的內聯指令。

我在手動進行循環展開時得到了巨大的性能提升,你知道為什么會這樣嗎?

在手動進行循環展開時,獲得顯著的性能提升的原因可能與多個因素有關。循環展開是一種優化技術,通過減少循環控制開銷和增加指令級并行性來提高程序的執行效率。

要理解為什么循環展開會帶來這么大的性能提升,通常需要查看具體的代碼和展開前后的匯編語言。這可以幫助分析哪些部分的執行被優化了,從而加速了程序的運行。

在進行循環展開時,編譯器需要處理更多的計算任務,并減少了在每次迭代中執行的控制語句(例如分支判斷或循環計數更新)的次數。這樣可以減少不必要的跳轉和延遲,從而提升執行速度。

通過查看展開前后的匯編代碼,可能會發現,展開后的代碼能更好地利用CPU的流水線和并行執行能力,減少了循環中間的中斷或等待時間。此外,某些硬件架構也可能對展開后的代碼優化更為友好,因此會進一步加速程序的執行。

因此,手動進行循環展開有時能夠通過直接減少循環控制的開銷,充分利用CPU的并行處理能力,從而實現顯著的性能提升。

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

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

相關文章

python將字符串轉成二進制數組

python將字符串轉成二進制數組 功能概述: save_binary_to_json() 函數:將字符串轉換為二進制數據(字節的整數表示),并保存到JSON文件中。 load_binary_from_json() 函數:從JSON文件中讀取二進制數據并還原…

【springboot知識】配置方式實現SpringCloudGateway相關功能

配置方式實現SpringCloudGateway相關功能 Spring Cloud Gateway 核心功能與實戰實現指南一、核心功能架構二、六大核心功能實現1. 動態路由配置2. 斷言(Predicate)配置3. 過濾器(Filter)配置4. 負載均衡配置5. 熔斷降級配置6. 限流配置 三、高級配置技巧1. 跨域配置2. 重試機制…

Windows 10 環境二進制方式安裝 MySQL 8.0.41

文章目錄 初始化數據庫配置文件注冊成服務啟停服務鏈接服務器登錄之后重置密碼卸載 初始化數據庫 D:\MySQL\MySQL8.0.41\mysql-8.0.41-winx64\mysql-8.0.41-winx64\bin\mysqld -I --console --basedirD:\MySQL\MySQL8.0.41\mysql-8.0.41-winx64\mysql-8.0.41-winx64 --datadi…

流式通信技術對比:SSE vs WebSocket 應用場景與最佳實踐

在構建動態、實時交互的現代 Web 應用時,實時通信能力至關重要。Server-Sent Events(SSE) 和 WebSocket 是當前最主流的兩種技術方案,它們都支持服務器主動向客戶端推送數據,但在通信模式、應用場景和實現復雜度上存在…

復雜度和順序表(雙指針方法)

目錄 目錄 目錄 前言: 一、時間復雜度和空間復雜度 1.1概念 1.2規則 二、順序表 2.1靜態順序表 2.2動態順序表 三、雙指針法 四、總結 前言: 時間復雜度和空間復雜度是用于判斷算法好壞的指標,程序性能的核心指標。時間復雜度主要衡…

flutter 專題 六十四 在原生項目中集成Flutter

概述 使用Flutter從零開始開發App是一件輕松愜意的事情,但對于一些成熟的產品來說,完全摒棄原有App的歷史沉淀,全面轉向Flutter是不現實的。因此使用Flutter去統一Android、iOS技術棧,把它作為已有原生App的擴展能力,…

Java高階程序員學習計劃(詳細到天,需有一定Java基礎)

??致敬讀者 ??感謝閱讀??笑口常開??生日快樂?早點睡覺??博主相關 ??博主信息??博客首頁??專欄推薦??活動信息文章目錄 Java高階程序員學習計劃(詳細到天,需有一定Java基礎)第一階段(30天)Java基礎:Java生態工具鏈:設計模式與編碼規范:第二階段(15天…

JS自動化獲取網站信息開發說明

一、自動獲取信息的必要性 1. 提高效率與節省時間 批量處理:自動化可以快速抓取大量數據,比人工手動操作快得多。 24/7 運行:自動化工具可以全天候工作,不受時間限制。 減少重復勞動:避免人工反復執行相同的任務&am…

Android Kotlin 依賴注入全解:Koin appModule 配置與多 ViewModel 數據共享實戰指南

一、基礎配置與概念 1. 什么是 appModule appModule 是 Koin 依賴注入框架中的核心配置模塊,用于集中管理應用中的所有依賴項。它本質上是一個 Koin 模塊(org.koin.core.module.Module),通過 DSL 方式聲明各種組件的創建方式和依…

學習記錄:DAY21

我的開發日志:類路徑掃描、DI 容器與動態代理 前言 我失憶了,完全不記得自己早上干了什么。 日程 早上 10 點左右開始,學了一早上,主要是類路徑掃描相關的調試。 晚上 8 點了,真不能再摸🐟了。 學習記錄 計…

【Agent】MCP協議 | 用高德MCP Server制作旅游攻略

note MCP (Model Context Protocol) 代表了 AI 與外部工具和數據交互的標準建立。MCP 的本質:它是一個統一的協議標準,使 AI 模型能夠以一致的方式連接各種數據源和工具,類似于 AI 世界的"USB-C"接口。 它能夠在 LLM/AI Agent 與外…

使用 Spring Data Redis 實現 Redis 數據存儲詳解

使用 Spring Data Redis 實現 Redis 數據存儲詳解 Spring Data Redis 是 Spring 生態中操作 Redis 的核心模塊,它封裝了 Redis 客戶端的底層細節(如 Jedis 或 Lettuce),提供了統一的 API 來操作 Redis 的數據結構。以下是詳細實現…

Qt5與現代OpenGL學習(四)X軸方向旋轉60度

把上面兩張圖像放到D盤1文件夾內&#xff1a; shader.h #ifndef SHADER_H #define SHADER_H#include <QDebug> #include <QOpenGLShader> #include <QOpenGLShaderProgram> #include <QString>class Shader { public:Shader(const QString& verte…

【Machine Learning Q and AI 讀書筆記】- 02 自監督學習

Machine Learning Q and AI 中文譯名 大模型技術30講&#xff0c;主要總結了大模型相關的技術要點&#xff0c;結合學術和工程化&#xff0c;對LLM從業者來說&#xff0c;是一份非常好的學習實踐技術地圖. 本文是Machine Learning Q and AI 讀書筆記的第2篇&#xff0c;對應原…

using var connection = connectionFactory.CreateConnection(); using var 是什么意思

在 .NET 中&#xff0c;??垃圾回收&#xff08;Garbage Collection, GC&#xff09;?? 確實是自動管理內存的機制&#xff0c;但它 ??僅適用于托管資源&#xff08;Managed Resources&#xff09;??&#xff08;如類實例、數組等&#xff09;。然而&#xff0c;對于 ?…

Multicore-TSNE

文章目錄 TSNE使用scikit-learn庫使用Multicore-TSNE庫安裝方法基本使用方法采用不同的距離度量 其他資料 TSNE t-Distributed Stochastic Neighbor Embedding (t-SNE) 是一種高維數據的降維方法&#xff0c;由Laurens van der Maaten和Geoffrey Hinton于2008年提出&#xff0…

SI5338-EVB Usage Guide(LVPECL、LVDS、HCSL、CMOS、SSTL、HSTL)

目錄 1. 簡介 1.1 EVB 介紹 1.2 Si5338 Block Diagram 2. EVB 詳解 2.1 實物圖 2.2 基本配置 2.2.1 Universal Pin 2.2.2 IIC I/F 2.2.3 Input Clocks 2.2.4 Output Frequencies 2.2.5 Output Driver 2.2.6 Freq and Phase Offset 2.2.7 Spread Spectrum 2.2.8 快…

Spring AI應用系列——基于OpenTelemetry實現大模型調用的可觀測性實踐

一、項目背景與目標 在AI應用日益復雜的今天&#xff0c;大模型服務&#xff08;如語言理解和生成&#xff09;的性能監控和問題排查變得尤為關鍵。為了實現對大模型調用鏈路的可觀測性&#xff08;Observability&#xff09;管理&#xff0c;我們基于 Spring Boot Spring AI…

Spyglass:官方Hands-on Training(一)

相關閱讀 Spyglasshttps://blog.csdn.net/weixin_45791458/category_12828934.html?spm1001.2014.3001.5482 本文是對Spyglass Hands-on Training中第一個實驗的翻譯&#xff08;有刪改&#xff09;&#xff0c;Lab文件可以從以下鏈接獲取。Spyglass Hands-on Traininghttps:…

PCB設計工藝規范(三)走線要求

走線要求 1.走線要求2.固定孔、安裝孔、過孔要求3.基準點要求4.絲印要求 1.走線要求 印制板距板邊距離:V-CUT 邊大于 0.75mm&#xff0c;銑槽邊大于0.3mm。為了保證 PCB 加工時不出現露銅的缺陷&#xff0c;要求所有的走線及銅箔距離板邊:V-CUT邊大于 0.75mm&#xff0c;銑槽邊…