游戲引擎學習第246天:將 Worker 上下文移到主線程創建

回顧并為今天的工作做準備

關于GPU驅動bug的問題,目前本地機器上沒有復現。如果有問題,昨天的測試就應該已經暴露出來了。當前演示的是游戲的過場動畫,運行正常,使用的是硬件渲染。
之前使用軟件渲染時沒有遇到太多問題,這是因為軟件渲染不會直接使用GPU,自然不會觸發復雜的兼容性問題。而一旦開始使用GPU,就容易遇到兼容性問題。這是因為與GPU通信的協議本身就非常復雜和混亂,而且不同GPU硬件及其驅動程序每年都會有很大變化,因此一旦使用GPU,出現兼容性問題的概率就非常高。
如果不涉及GPU,大部分情況下兼容性問題很少,但現代游戲為了在圖形性能上具有競爭力,基本上必須大量使用GPU,因為GPU上提供了更多高效的圖形功能。

根據論壇上的反饋,在NVIDIA顯卡上,如果使用多線程下載紋理,會出現問題。調查后發現,問題出在OpenGL上下文(OpenGL context)的創建方式上。
具體來說,如果所有需要的OpenGL上下文都在主線程創建好,然后再分發給各個子線程使用,那么一切運行正常;但是如果在各個子線程中分別創建OpenGL上下文,就會出問題。

目前還不清楚具體原因,已經將源代碼發送給了NVIDIA,希望能得到官方解釋。但即使沒有明確解釋,也能推測可能是因為OpenGL在內部使用了線程局部存儲(Thread Local Storage),主線程調用OpenGL時初始化了某些關鍵數據,而其他線程由于沒有這些初始化數據,導致無法正常創建上下文。不過這只是猜測,具體機制還不得而知。

可以確定的是,直接在子線程上新建OpenGL上下文是不可靠的,只能在主線程上統一創建好后再分發使用。這也是PC平臺開發的常態:硬件環境極其復雜,遇到的兼容性問題很多時候沒辦法徹底搞清楚,只能通過觀察和實驗總結經驗并找到解決辦法。

這次的問題幸運地相對簡單。解決方案是:

  • 在主線程上,預先為每個需要的子線程創建好OpenGL上下文。
  • 啟動子線程時,將已經創建好的OpenGL上下文傳遞給它們,由子線程直接使用預建的上下文。

接下來回顧之前的實現方式。
初始化OpenGL的代碼集中在 Win32InitOpenGL 函數中,然后在子線程中調用 Win32CreateOpenGLContextForWorkerThread 來為子線程單獨創建OpenGL上下文。
問題就在于,Win32CreateOpenGLContextForWorkerThread 是在子線程里被調用的,而這正是導致兼容性問題的原因。

因此,需要修改流程:

  • 在啟動子線程之前,在主線程一次性調用創建所有所需的OpenGL上下文。
  • 然后把這些上下文傳遞給子線程,子線程不再自己創建上下文,只負責綁定和使用傳入的上下文。

這樣可以確保代碼在NVIDIA驅動和其他廠商的驅動上都能正常運行,兼容性問題得到解決。


win32_game.h:將 platform_work_queue 從 win32_game.cpp 中移入

接下來要做的事情很直接,并不復雜。
主要需要改動的是,在啟動線程時,除了傳遞平臺工作隊列(PlatformWorkQueue),還需要給線程傳遞更完整、更多信息的數據結構。

目前的實現中,我們過于依賴平臺工作隊列,線程啟動時僅僅接收了一個PlatformWorkQueue對象。可以在代碼中看到,線程啟動函數ThreadProc,接收了一個PlatformWorkQueue指針。這個隊列是多個線程共享的,它只負責調度需要執行的任務。

因為這個隊列是共享的,所以無法通過它來為每個線程分別傳遞獨立的數據,比如每個線程需要綁定的OpenGL上下文。每個線程需要使用自己獨立的數據,這就要求啟動線程時傳遞一個專用的、包含所有必要信息的結構體。

實際上,在代碼中早就有了這樣一個結構體,只是之前遺留下來了,沒有充分利用。這個結構體叫做Win32ThreadStartup。之前在做多線程處理時,曾經考慮過需要它,所以這個結構體已經存在。

Win32ThreadStartup本來就是為了在創建線程時傳遞更豐富的信息而設計的。
現在只需要擴展使用它,除了傳遞線程應該使用的PlatformWorkQueue之外,還可以附加每個線程獨立需要的數據,比如對應的OpenGL渲染上下文(OpenGL RC,Render Context)。

因此,修改思路總結如下:

  1. 將原本直接傳遞PlatformWorkQueue的地方,改成傳遞Win32ThreadStartup結構體。
  2. Win32ThreadStartup中,除了包含工作隊列,還包含該線程需要使用的OpenGL上下文。
  3. 啟動線程時,每個線程根據自己收到的Win32ThreadStartup信息,拿到對應的工作隊列和OpenGL上下文,進行正常工作。

這就是全部需要做的事情了,整體改動并不大,但能解決線程之間獨立數據傳遞的問題,同時也能解決之前提到的由于子線程自己創建OpenGL上下文導致的兼容性問題。
在這里插入圖片描述

之前做過了

在這里插入圖片描述

在這里插入圖片描述

win32_game.cpp:在 ThreadProc 中初始化線程和隊列,并更改為測試以確保我們擁有 OpenGLRC


接下來需要繼續完成線程啟動流程的調整。
具體要做的是:在線程啟動函數ThreadProc中,不再直接使用PlatformWorkQueue作為參數,而是使用更完整的Win32ThreadStartup結構體作為啟動參數。這個結構體不僅包含了原先需要的工作隊列,還可以附帶每個線程獨有的數據,比如OpenGL上下文。

具體步驟如下:

  • 線程啟動時,接收參數并將其強制轉換為Win32ThreadStartup指針。
  • 原本直接從參數中提取工作隊列的代碼,需要調整成從Win32ThreadStartup結構體中提取。
  • 這樣雖然仍然是傳遞了工作隊列,但它現在被包含在了一個更完整的啟動數據包里,因為后續還需要更多線程專屬的信息,比如OpenGL的RC(Render Context)。

接著,要處理線程內的OpenGL上下文綁定:

  • 刪除原來在工作隊列里標記需要OpenGL的邏輯。
  • 在線程啟動時,判斷傳入的啟動參數中是否包含了OpenGL的RC。
  • 如果有RC,需要調用wglMakeCurrent,將當前線程的OpenGL上下文切換成對應的RC。

注意,為了能調用wglMakeCurrent,還需要一個有效的DC(Device Context,設備上下文)。雖然這些線程本身不會直接在窗口上渲染,只是提交紋理等后臺工作,但Windows要求必須有一個有效的DC傳進去。

這里參考了之前查詢到的資料,發現處理多線程紋理下載時,通常做法是繼續傳遞一個現有的、有效的DC,而不是自己去新建一個。因此計劃是,將原來已有的DC傳遞給線程,用于綁定OpenGL上下文。

所以,需要在Win32ThreadStartup結構體中加入DC的字段,比如叫做OpenGLDC,并在線程啟動時一起傳遞進去。線程內部就可以通過這個DC和RC正確調用wglMakeCurrent,完成OpenGL上下文的切換。

總結下來,目前已經完成的內容是:

  1. 修改線程啟動時的參數,從PlatformWorkQueue切換成Win32ThreadStartup
  2. 在線程啟動函數中,從Win32ThreadStartup中提取工作隊列,同時提取OpenGL的RC和DC。
  3. 在子線程中,根據RC和DC正確設置OpenGL上下文。
  4. 移除原先在工作隊列上處理OpenGL標記的邏輯。

接下來只需要在創建線程時,正確地構建這個包含所有信息的Win32ThreadStartup數據包,并傳給每個線程就可以了。


win32_game.cpp:讓 Win32MakeQueue 接受 win32_thread_startup


接下來需要處理線程啟動調用的地方。
在原本的代碼中,調用ThreadProc時,只簡單地傳遞了一個工作隊列參數。現在由于需要傳遞更復雜的線程初始化信息,包括OpenGL上下文和DC,所以必須改為傳遞Win32ThreadStartup結構體實例。

具體調整方式如下:

  • 在創建線程時,不再只傳遞單純的工作隊列,而是傳遞一個Win32ThreadStartup實例。
  • 因為每個線程需要持有自己的啟動信息,所以要提前創建一個Win32ThreadStartup數組,每個元素對應一個線程。
  • 啟動線程時,將對應的Win32ThreadStartup實例傳給線程。

這樣一來,每個線程就能通過自己的啟動參數拿到獨立的OpenGL上下文和必要的設備上下文(DC)。

為了簡化調用方的操作,增加了一個小優化:

  • 在線程創建函數內部,如果調用方沒有特別指定工作隊列,可以在內部自動為Win32ThreadStartup填充正確的工作隊列字段。
  • 這樣調用方在一般情況下仍然可以像以前一樣簡單使用,不需要手動設置工作隊列,只在需要自定義OpenGL資源的情況下再做額外配置。

總結到目前為止,完成了以下內容:

  1. 確認每個線程需要獨立的Win32ThreadStartup參數,包含工作隊列、OpenGL RC、OpenGL DC。
  2. 修改線程啟動函數,接受并解析Win32ThreadStartup
  3. 在創建線程時,維護一個Win32ThreadStartup數組,并傳入正確的數據。
  4. 添加內部便利邏輯,讓不關心OpenGL細節的調用方可以自動填充工作隊列。
  5. 發現并記錄了項目編譯設置的小問題,留待后續處理。

win32_game.cpp:初始化一些高優先級和低優先級線程,并使前兩個 LowPriStartups 由 Win32GetThreadStarupForGL 填充


在之前注釋掉MakeQueue調用時,實際上還沒有對應的線程啟動參數(startups)。
為了修正這個問題,現在需要手動創建這些Win32ThreadStartup實例。
具體做法是:

  • 定義一個high_pri_startups數組,用來存儲高優先級線程的啟動信息,一共有6個。
  • 使用這個數組的元素數量來決定需要創建多少高優先級線程。
  • 傳遞這個startups數組的指針給線程啟動邏輯。

對于低優先級隊列(Low Priority Queues):

  • 這些隊列的線程需要額外附帶OpenGL上下文信息。
  • 每個低優先級線程需要擁有一個獨立的OpenGL上下文(RC)。
  • 所以還需要一個單獨的low_pri_startups數組,數量對應低優先級線程數量(這里是2個)。

在處理低優先級線程時:

  • 要為每一個低優先級線程初始化一套專屬的啟動參數,里面包含獨立的OpenGL DC和RC。
  • 這里的DC(設備上下文)和RC(渲染上下文)可以在局部變量中生成,不再需要是全局變量,這樣代碼結構更清晰。

具體步驟是:

  1. 調用一個新的封裝函數,比如CreateWin32ThreadStartupGL,傳入需要的DC和RC。
  2. 這個函數內部完成必要的OpenGL初始化并返回一個Win32ThreadStartup結構體實例。
  3. 調用兩次該函數,分別為兩個低優先級線程生成啟動參數。

接下來,在線程創建的時候,只需要傳入這些預先準備好的startups即可,而不是臨時在調用時構造。

總結來說,完成了如下調整:

  • 引入了high_pri_startupslow_pri_startups數組,管理不同優先級線程的啟動信息。
  • 每個低優先級線程都綁定了獨立的OpenGL上下文,避免線程沖突。
  • OpenGL資源(DC、RC)改為局部管理,降低全局污染。
  • 計劃編寫一個新的封裝函數,專門處理低優先級線程需要的OpenGL上下文初始化邏輯。
  • 整體思路是將原本分散的初始化過程集中到一個合理的、模塊化的地方管理,讓代碼更規范。

最后,定位到需要參考之前的Win32CreateContextForWorkerThread函數,將它的邏輯提取、包裝成新的初始化函數,方便直接生成線程啟動參數。


移除全局變量
在這里插入圖片描述

Win32CreateOpenGLContextForWorkerThread(); 可以刪掉
在這里插入圖片描述

win32_game.cpp:用返回 win32_thread_startup 的函數替換 Win32GetThreadStarupForGL


我們的目標是用一個新的函數替換之前的函數。新的函數要實現的功能和原來完全一樣,但區別在于,它現在會把結果打包成一個線程啟動結構,方便后續直接用于線程啟動。

現在開始整理邏輯:

  • 我們已經實現了一個新的上下文啟動邏輯,它會幫我們準備好線程啟動時所需的OpenGL環境。
  • 原先手動設置的wglCreateContext返回值(舊的DC)已經不再需要,因為現在通過新的機制可以獲取一個OpenGLDC,直接使用它。
  • 共享上下文(Shared Context)也由新的函數傳入并綁定,所以我們也不再需要顯式地維護那個共享RC。
  • 為了命名更清晰,我們將共享RC命名為shared_context,并把它傳入創建函數用于生成線程專屬的RC。
  • 所以舊的初始化過程中一大堆關于DC和RC管理的中間變量和流程現在都可以移除掉了。

最終邏輯簡化為:

  1. 調用新的線程啟動封裝函數。
  2. 該函數返回一個結構體,內部已經包含初始化好的OpenGL設備上下文(DC)與渲染上下文(RC)。
  3. 我們只需將這個結構體作為線程的啟動參數傳入即可。

結果結構中:

  • result.opengl_dc 指向新生成的OpenGL DC。
  • result.opengl_context 是通過共享上下文派生出來的新RC。
  • 整個創建過程變得更簡潔清晰,并且線程上下文初始化成為一個自動化操作。

此外,之前存在的“是否需要OpenGL”這種布爾標記也變得多余,因為現在如果我們需要線程使用OpenGL,就直接傳入相關參數結構體;不需要的話,就傳空。線程可以通過是否存在OpenGL上下文字段來自動判斷,無需再人為打標簽。

綜上所述:

  • 完成了初始化函數的封裝與替換。
  • 簡化了上下文的管理邏輯。
  • 使線程啟動參數變得結構化、可維護。
  • 移除了冗余的OpenGL相關判斷邏輯,使系統更加模塊化。

ThreadStartup2 的賦值封裝到函數里面去
在這里插入圖片描述

網絡:查看 WGL_ARB_pixel_format 文檔


我們在OpenGL部分還有一個細節尚未處理,那就是在設置幀緩沖區(Frame Buffer)像素格式時,是否應該顯式請求sRGB支持。

之前我們在創建幀緩沖區時,通過傳遞一個布爾值 true 的方式,請求了 FRAMEBUFFER_SRGB_CAPABLE_ARB 屬性,用于啟用sRGB顏色空間支持。按照我們目前的理解,這樣傳參的行為,是資源申請階段向驅動聲明我們想要啟用sRGB的方式。

但我們一直沒有驗證過:如果顯式聲明了 FRAMEBUFFER_SRGB_CAPABLE_ARB,但當前驅動并不支持該功能,會發生什么后果?是函數直接失敗、引發錯誤,還是靜默忽略該參數?這一點我們不確定。

于是我們回到代碼中查看使用的API——wglChoosePixelFormatARB。這個函數是我們用來選擇像素格式的核心,它接受兩個參數列表:

  1. piAttribIList:要申請的屬性ID。
  2. piAttribFList:對應屬性的期望值。

我們此前在這兩個列表中都填寫了數值,但發現其實在某些屬性上(比如浮點屬性),可以填寫為0表示未指定,避免意外報錯。這一點之前沒有注意,現在發現其實應該這么做,算是一個小的修正點。

繼續查看官方文檔,可以確認函數在如下情況下會失敗:

  • 傳入了無效的屬性ID;
  • 屬性列表中的值非法;
  • 或者HDC(設備上下文)本身非法。

文檔中明確指出:“如果屬性列表中包含非法的屬性或設備上下文無效,會直接返回錯誤。” 這說明我們不能盲目傳入 FRAMEBUFFER_SRGB_CAPABLE_ARB,除非我們事先知道驅動確實支持該功能。

因此我們得出的結論是:不能默認傳入該屬性。必須在使用前通過擴展檢測機制確認當前驅動支持 WGL_ARB_framebuffer_sRGB,否則可能會直接導致函數調用失敗甚至崩潰。

雖然這很麻煩,但確實是必須處理的事情。我們將調整邏輯:

  • 在設置像素格式前,先查詢是否支持 WGL_ARB_framebuffer_sRGB
  • 若支持,則再傳入 FRAMEBUFFER_SRGB_CAPABLE_ARB
  • 若不支持,則忽略該參數。

總的來說:

  • 之前認為可能會被忽略的未知屬性,實際上會導致函數失敗;
  • 這意味著我們必須顯式檢測擴展是否存在;
  • 盡管流程繁瑣,但為了確保穩定性與兼容性,這是唯一可靠的做法;
  • 后續將加入對應的檢測邏輯,避免在不支持的環境中觸發錯誤。

https://registry.khronos.org/OpenGL/extensions/
https://registry.khronos.org/OpenGL/extensions/ARB/
在這里插入圖片描述

game_opengl.cpp:考慮如何獲取 wgl 擴展

在OpenGL中,有一種方法可以用來查詢擴展功能,通常是通過 glGetString(GL_EXTENSIONS) 來完成的。但是,這個方法僅適用于查詢OpenGL擴展,而不能查詢WGL(Windows OpenGL擴展)擴展。因此,我們無法直接知道WGL擴展的情況。

要查詢WGL擴展,需要使用 wglGetExtensionsString,這是一個不同于 glGetString(GL_EXTENSIONS) 的函數。因此,我們必須調用 wglGetExtensionsString 來獲取WGL擴展的詳細信息。

另外,雖然我們可以直接檢查OpenGL擴展列表來看看是否支持 GLX_FRAMEBUFFER_SRGB,如果存在這個擴展,那么很可能系統就支持該功能。但實際上,由于OpenGL允許將紋理用作幀緩沖,因此即使某個實現聲明支持 GL_FRAMEBUFFER_SRGB,也不能保證它在實際的幀緩沖上支持該擴展,可能只在渲染到紋理(render to texture)時才支持這個擴展。

為了做完全的驗證,最好還是執行一個額外的步驟,去查詢WGL擴展字符串,確認它是否支持這個功能。雖然這個過程有些麻煩,但為了確保功能的正確性,還是需要進行這個檢查。

總的來說,為了驗證是否支持特定的WGL擴展,必須用 wglGetExtensionsString 來進行查詢,避免僅依賴于OpenGL擴展的檢查。

網絡:查看 WGL_EXT_extensions_string 文檔,確認要檢查是否有 wgl 擴展,必須先有 wgl 擴展

為了檢查WGL擴展是否存在,首先需要注意的是,操作過程比較麻煩,因為在檢查一個特定的WGL擴展之前,我們必須首先調用 wglGetExtensionsString 來獲取擴展的字符串。這個方法返回一個包含所有WGL擴展的字符串。

問題在于,在調用 wglGetExtensionsString 之前,我們無法直接檢查WGL擴展是否存在,因為字符串本身并未加載。因此,唯一的辦法就是調用 wglGetExtensionsString,并獲取一個指向擴展字符串的指針。之后,再根據返回的內容來判斷是否存在我們想要的擴展。

簡而言之,在獲取擴展字符串之前,我們無法知道特定擴展是否存在,因此只能通過調用該函數并檢查返回的字符串來確定是否有該擴展。這種做法雖有些麻煩,但也是目前可行的解決方案。

win32_game.cpp:使用 wglGetExtensionsStringEXT 查看我們有哪些擴展字符串

首先,在檢查是否支持某個WGL擴展時,遇到了一個有趣的問題。為了檢查某個擴展是否存在,必須通過 wglGetExtensionsString 函數獲取擴展的字符串。如果返回的字符串中包含所需的擴展信息,那么就可以確定該擴展是否被支持。問題在于,不能直接查詢這個擴展是否存在,因為沒有辦法預先知道擴展字符串是否已經加載。

接下來,我們通過調用 wglGetExtensionsString 獲取擴展字符串后,使用一個循環檢查字符串中是否包含特定的擴展。如果找到了對應的擴展,就會設置相應的標志(例如SRGB支持的標志)。為了實現這一點,需要在代碼中搜索是否包含目標擴展字符串。如果找到了,就可以設置標志為 true,表示該擴展被支持。

同時,由于這段代碼涉及到多個全局變量,在操作時為了提高代碼的可讀性和易維護性,可以考慮將這些全局變量封裝到一個結構體中,這樣能讓代碼更清晰。

如果找到所需的擴展,接著會根據支持的擴展修改后續的處理邏輯。例如,若發現OpenGL支持特定的 SRGB 幀緩沖擴展,則在傳遞給后續函數時需要確保該擴展已經啟用。為此,如果OpenGL不支持該擴展,可以選擇將相關的設置清除掉。

最后,代碼中還需要查詢具體的OpenGL支持的擴展字符串,這樣可以進一步確認哪些特性在當前環境中可用。為了完整性,仍然需要查明OpenGL中具體的擴展標識符(例如framebuffer sRGB的擴展標志),以便正確判斷和設置。

https://registry.khronos.org/OpenGL/extensions/EXT/WGL_EXT_extensions_string.txt
在這里插入圖片描述

在這里插入圖片描述

在這里插入圖片描述

在這里插入圖片描述

網絡:查看 ARB_framebuffer_sRGB

首先,需要檢查OpenGL擴展是否支持framebuffer sRGB。如果在擴展字符串中找到了這個標識符,那么可以確認當前環境支持framebuffer sRGB,從而繼續后續操作。

接著,在獲取這個擴展字符串之后,繼續檢查是否可以安全地使用該擴展。如果找到了framebuffer sRGB的支持標志,就可以確保后續的圖形操作能夠正確處理帶有sRGB顏色空間的幀緩沖。

在此過程中,還需要確定獲取擴展字符串的函數原型,并將其正確地引入到代碼中。為此,可以使用 wglGetExtensionsString 函數獲取擴展信息。如果之前未定義該函數的原型,需要確保在代碼中聲明并使用它。

另外,有一點小的修改是,將變量名更改為更具描述性的名稱,例如將與擴展相關的變量命名為 extensionString,這樣可以提高代碼的可讀性和易于維護性。

通過這些步驟,代碼可以順利執行,確保正確處理和檢測OpenGL擴展,尤其是framebuffer sRGB的支持,并根據檢測結果執行相應的操作。

調試器:檢查我們的擴展

首先,通過查看擴展字符串代碼,成功獲得了圖形卡的擴展信息。根據輸出,卡支持多個OpenGL擴展,包括像wglChoosePixelFormatARBGL_ARB_framebuffer_sRGB等功能。特別地,framebuffer_sRGB擴展存在,并且被正確檢測到。這是一個好消息,表示當前的硬件和驅動支持sRGB幀緩沖。

但是,在解析過程中發現了一些問題,最初代碼沒有檢測到所需的擴展字符串,導致未能正確設置相關標志。通過修正后,發現并非所有擴展字符串都匹配,特別是ARB版本和EXT版本之間的差異,后者更常見且支持的范圍更廣。因此,應該檢查EXT版本,而不是ARB版本。

修改代碼后,成功地檢測到支持的擴展,并且相關的標志被正確地設置為1,表示該功能被啟用。這表明代碼已經順利地識別并啟用了sRGB幀緩沖,且沒有破壞其他兼容性設置,所有操作都按預期進行。

至于接下來的步驟,考慮到現在已經沒有更多的OpenGL相關任務需要處理,除了完成一些后續的小調整外,當前階段似乎沒有其他更緊急的事項。剩余的時間可以用于驗證或測試已有的功能,確保各項操作的穩定性。
在這里插入圖片描述

在這里插入圖片描述

WGL_EXT_depth_float WGL_ARB_buffer_region WGL_ARB_extensions_string WGL_ARB_make_current_read WGL_ARB_pixel_format WGL_ARB_pbuffer WGL_EXT_extensions_string WGL_EXT_swap_control WGL_ARB_multisample WGL_ARB_pixel_format_float WGL_ARB_framebuffer_sRGB WGL_ARB_create_context WGL_ARB_create_context_profile WGL_EXT_pixel_format_packed_float WGL_EXT_create_context_es_profile WGL_EXT_create_context_es2_profile WGL_NV_DX_interop WGL_NV_DX_interop2 WGL_ARB_robustness_application_isolation WGL_ARB_robustness_share_group_isolation WGL_ARB_create_context_robustness WGL_ARB_context_flush_control
在這里插入圖片描述

todo.txt:查看 TODO 列表

當前查看了任務列表,注意到有一項是“在重新加載DLL之前刷新所有線程隊列”。這是一個可以嘗試去完成的任務,感覺在剩余的時間內實現的可能性比較大。

回顧之前的內容,發現關于調試代碼、音頻評估渲染以及硬件渲染的工作已經大部分完成。其中包括了對sRGB幀緩沖和紋理相關的處理,還有紋理數據下載的優化。不過,關于“渲染到紋理(render to texture)”這一塊,目前還沒有真正去實現。雖然可以現在嘗試去做,但由于現階段沒有現成的系統功能使用到“渲染到紋理”,而且設計一個用于測試的渲染到紋理的用例可能也比較復雜,考慮到只剩下大約二十五分鐘,貿然開始渲染到紋理的工作可能超出時間范圍,因此不太適合現在進行。

簡單回顧“渲染到紋理”的概念,就是不再直接渲染到屏幕幀緩沖,而是渲染到一個紋理對象,以便后續渲染步驟中作為源紋理進行采樣使用。雖然概念簡單,但在OpenGL中實際操作涉及較多細節配置,因此實施起來容易變得繁瑣。

綜合考慮,當前決定優先完成“在重新加載DLL之前刷新所有線程隊列”這一任務。這個任務涉及的范圍局限在現有的平臺抽象層(Win32層),屬于當前的工作區域內,不需要大幅度切換環境,預計也不會像渲染到紋理那樣復雜,因此在剩余時間內完成的可能性更高。

總結來說,目前的計劃是:

  • 不進行渲染到紋理的開發,留待后續更充分的時間來處理;
  • 優先處理“刷新所有線程隊列以便在重新加載DLL前保持一致性”的工作;
  • 保持在Win32平臺層工作,繼續完成剩余的小型、收尾性質的任務,以便整體流程更加完整、穩健。

運行游戲并測試熱重載功能

目前我們正在查看“在重新加載 DLL 前刷新所有線程隊列”的具體實現情況,并嘗試理解目前的實時代碼加載(Live Code Reloading)機制是否已經完善,是否會帶來潛在問題。

首先做了一個簡單的測試:直接在編輯器中重新編譯當前的 DLL,并觀察游戲是否還能正常運行。測試表明游戲依然運行正常,說明 DLL 的熱重載機制基本是有效的。

接下來為了進一步驗證,修改了 game.cpp 中的一些與角色渲染相關的代碼片段,并重新編譯。這次我們可以通過屏幕上的可視化變化來驗證熱重載是否起效。結果也證明,在修改代碼并重新編譯后,游戲確實反映了代碼變化,說明熱重載機制在大部分情況下是正常工作的。

不過目前存在兩個已知的問題,我們需要修復:


第一個問題:調試系統中字符串指針失效

調試系統中存在的問題是,當 DLL 被重新加載后,調試系統可能仍然保留了指向舊 DLL 中字符串的指針。這是因為字符串本身是存儲在 DLL 的地址空間中的,而 DLL 被重新加載后,原來的地址可能就不再有效了,因此不能再使用舊的指針訪問字符串數據。

這可能導致調試菜單中的字符串渲染異常,甚至出現錯誤或崩潰。

正確的做法應該是在 DLL 重載前,將調試系統中引用的字符串復制出來,或者在重載后重新初始化需要的字符串內容,確保指針始終指向有效的內存。


第二個問題:任務隊列(Work Queue)中使用了函數指針

另一個問題出現在任務隊列(Work Queue)的實現中。

當前的多線程任務系統允許主線程將任務加入任務隊列,并由工作線程異步執行。這些任務本質上是以函數指針的方式被調度執行的。然而這些函數指針也往往指向的是 DLL 中的函數實現。一旦 DLL 被重新加載,原來的函數地址將不再有效,從而使得工作線程中殘留的任務指針失效,造成程序錯誤甚至崩潰。

目前代碼中存在一個處理任務隊列的函數 Win32DoNextWorkQueueEntry,這部分代碼就是任務調度和執行的關鍵。這里如果沒有在 DLL 熱重載前清空所有隊列任務,或沒有同步等待所有工作線程完成,都會造成安全隱患。


當前的結論和后續計劃

目前來看,熱重載機制基本可用,但存在兩個關鍵的潛在bug:

  1. 調試系統字符串未做內存安全轉移
  2. 任務隊列中函數指針未做線程同步清理

接下來的目標是修復這兩個問題。優先可以處理第二個問題:在重新加載 DLL 前刷新所有線程隊列。我們將確保在 DLL 被卸載之前,所有工作線程完成其任務,并清空任務隊列,避免使用到已失效的函數指針。

這項工作會集中在 Win32 平臺抽象層進行,屬于當前模塊的直接范圍內,適合在接下來的有限時間中完成。

win32_game.cpp:注意到 Entry.Callback 在不同版本之間位置不同,并考慮如何解決這個問題

當前我們正在深入分析 DLL 熱重載過程中的潛在問題,尤其關注任務隊列中的函數指針回調機制帶來的崩潰風險。


目前任務隊列中的每一個任務結構體都包含一個回調函數指針,該指針指向 DLL 中實現的具體函數。然而,這會在 DLL 被重新加載之后引發一個嚴重的問題:舊的任務隊列中可能還殘留著指向舊 DLL 中代碼的回調函數。當新的幀開始執行時,如果這些舊任務還未被清空,系統會調用這些已經失效的回調函數,導致跳轉到一段無效或錯誤的內存區域,從而引發程序崩潰或不確定行為。

也就是說,在舊 DLL 卸載、新 DLL 加載之間,如果線程還在執行舊任務中的回調邏輯,就會出現訪問非法地址的風險。目前的系統中并沒有機制來防止這種情況發生。


對此提出了兩個可能的解決方案:

解決方案一:取消函數指針,統一入口分發任務

思路是取消每個任務結構中使用函數指針的方式,轉而將所有任務的處理集中到一個統一的函數入口處。在這個統一入口函數中,通過一個 switchif-else 結構,根據任務類型的枚舉值跳轉到相應的任務處理邏輯。

這樣一來,所有任務執行的入口地址將固定,DLL 重載過程中不會涉及跳轉到非法函數地址的問題。

但該方案的缺點是:盡管任務入口地址固定了,但仍有可能存在另一個問題——某個工作線程正在執行 DLL 中的某段代碼邏輯(即使不是通過回調跳轉的方式),這時如果強制卸載 DLL,同樣會導致程序崩潰。因此,單靠統一入口無法完全避免潛在風險。


解決方案二:在重新加載 DLL 前等待所有任務執行完畢

這個方案是目前更可行也更安全的方式:

  1. 在執行 DLL 熱重載之前,先調用一個同步函數,等待所有工作隊列中的任務全部執行完成
  2. 對所有存在的工作隊列調用 Win32CompleteAllWork() 函數。
  3. 該函數會阻塞當前主線程,直到所有隊列任務都執行完畢,所有工作線程空閑。
  4. 一旦任務執行完成,就可以安全地卸載舊的 DLL 并加載新的 DLL。

這種方式能夠從根本上確保 DLL 被卸載時,沒有任何線程仍在執行其內部代碼,從而規避訪問非法代碼地址的崩潰問題。

目前在代碼中已經明確指出,DLL 重載操作應當在某處(例如調用 Win32LoadGameCode() 前)加入一個邏輯判斷或封裝,確保對所有任務隊列調用 Win32CompleteAllWork()


總結

熱重載過程中使用函數指針的任務調度方式存在天然風險,因為 DLL 卸載之后這些指針將變為無效。

解決這一問題的最直接辦法就是:在每次執行 DLL 重載之前,強制完成所有掛起任務,并確保所有線程空閑。這是目前最安全且實現簡單的方案。

接下來的計劃是,在熱重載流程中集成這一步驟,并驗證其在實際運行中是否能完全防止崩潰風險。

在這里插入圖片描述

新的 GL 代碼有個 bug,if(!),你加的這個檢查并沒有檢查 sRGB 變量,而是檢查了其他的變量

我們在調試過程中遇到了一些問題和反思,特別是關于 OpenGL 的 API 設計和實際使用中所帶來的復雜性與潛在的 bug 問題。以下是我們的詳細總結:


我們最初處理的是一個補全系統的 bug,最終發現確實是補全邏輯中的錯誤,這部分問題得到了確認。接著我們開始回顧之前在處理 OpenGL 的多線程紋理下載和幀緩沖相關代碼中所遇到的困難。

在這個過程中,我們想強調一個關鍵問題:OpenGL 設計中存在的一些不合理之處,直接導致了我們代碼中出現了大量沒有實際意義、卻極易出錯的處理邏輯。比如:

  • 為了處理紋理數據在多線程環境中的傳輸和繪制,我們寫了很多用于同步、緩沖管理、狀態判斷的代碼。
  • 這些代碼本質上并不增加程序的功能性,只是為了滿足底層 API 的調用需求。
  • 然而,這種 API 使用方式的復雜性為開發者帶來了大量容易犯錯的機會。

舉個例子,我們在使用某些 OpenGL 接口時,因為一個非常微妙的參數傳遞錯誤,導致了程序行為不符合預期。而這個錯誤本不應該發生——如果 API 設計得當,就應當允許我們直接調用接口并根據驅動是否支持自動決定是否執行。即便不支持,也應該有統一的查詢機制而不是靠開發者手動試錯。

這說明,API 的設計是否合理,會直接影響到開發者是否容易踩坑。一個好的 API 應該最小化這種“出錯的可能性”。


關于 Vulkan 的一點預告:

我們還提到,對于 Vulkan,我們持有類似但更嚴厲的批評。雖然 Vulkan 本身是更底層、更接近硬件的接口,但它在 API 設計時并未特別關注“減少出錯概率”這一點。相反:

  • 一個原本在 OpenGL 中可能只需要一次調用的操作,在 Vulkan 中往往需要幾十行代碼才能完成;
  • 這些代碼大多數不是邏輯相關內容,而是各種配置、狀態設置、描述結構體的填寫等;
  • 每一個細節都可能出錯,但 API 本身對這些出錯點幾乎沒有任何防護。

換句話說,Vulkan 并沒有在設計上提供任何“對錯誤免疫”的能力,反而為 bug 的產生提供了肥沃土壤。

我們認為,這種 API 設計理念是不負責任的。無論 API 是高層還是低層、通用還是專用,它都應當把“降低開發者出錯的概率”視為首要目標之一。因為:

  • 開發中 bug 是常態,不是例外;
  • API 如果不以減少 bug 為導向,那它就是在有意無意中推動 bug 的蔓延;
  • 更安全、更明確、更自適應的 API 設計,是減少整個系統不穩定性的關鍵。

最后我們注意到服務端可能出現了一些問題,比如游戲聊天系統可能停機了,這雖然是與 API 無關的外圍情況,但也提醒我們,一個系統的穩定性與其所有組件的設計質量息息相關。


總結:

  1. OpenGL 盡管在當年設計中已經算是合理,但其接口仍存在設計冗余、出錯概率高的問題。
  2. 通過一個典型的紋理渲染和下載流程,我們清楚地看到了這些設計上的缺陷帶來的多處隱患。
  3. 這反映出良好 API 設計的重要性:它不僅應當關注功能是否完整,更應關注如何減少開發者出錯的可能。
  4. Vulkan 更是將復雜性進一步放大,在提高自由度的同時大幅提升了 bug 的可能性。
  5. 一個優秀的 API 應該始終把“幫助開發者減少 bug”作為設計的核心目標之一。

我們接下來的工作重點仍會圍繞如何改進當前架構設計,提升使用安全性與開發效率。

在這里插入圖片描述

說真的,我覺得你應該寫一本 API 書。我從沒想過那么多事情

我們認真地探討了關于 API 設計的一些原則,并表達了編寫一本關于 API 設計書籍的想法。我們意識到,很多人其實并沒有真正理解或者采用我們所強調的這些設計理念,即使我們曾在講座中多次詳細闡述過這些內容。

我們覺得,確實可以寫一本關于 API 的書,因為這些內容非常實用,而且目前仍然很少有開發者真正貫徹這些原則。雖然我們不確定這樣的書會不會有很多人閱讀,但我們堅信它是有價值的。

在我們已有的一些資料中,已經包含了絕大多數關鍵的 API 設計原則:

  • 清晰性優先:API 接口必須易于理解,不應要求調用者具備隱晦的內部知識。
  • 容錯性設計:即使調用者使用錯誤的參數,也應避免引發嚴重錯誤,而是以安全方式返回錯誤碼或警告。
  • 狀態自管理:調用 API 不應依賴復雜的狀態前提,API 自己應能判斷當前是否允許操作。
  • 結構化擴展:新的功能不應破壞已有接口,通過版本號或結構體擴展支持未來需求。
  • 減少意圖歧義:每一個 API 函數的功能必須非常明確,避免做“一大堆事情”的函數設計。

我們認為,如果遵循這些原則,就能構建出非常好用且安全的 API 接口。我們自己在設計系統時,圍繞這些原則形成了一套清晰的思路,實踐中也證明了它們的有效性。

然而,現在的大多數開發者或系統設計者,依然沒有采用這些方法,導致了大量設計混亂、使用易錯的 API。我們深知這些問題的后果,因此希望通過整理和傳播這些知識,幫助更多人寫出真正好用、健壯的 API 接口。

總的來說,這是一種設計哲學,不僅僅是編程的技術細節。我們未來或許會更系統地整理這些內容,作為一本關于 API 設計的參考書。

關于鍵盤/手柄輸入,HalfTransitionCount 會超過 1 嗎?如果把輸入獲取放到另一個線程中,這樣能解決嗎?

我們探討了關于輸入處理(特別是游戲手柄、鍵盤和鼠標輸入)的實現細節,并分析了在不同線程和處理機制下,輸入事件的“計數”是否可能超過 1 的問題。

目前游戲手柄輸入是在每幀僅輪詢一次的狀態下進行的,因此它的事件數量最多為 1,不會積累多個輸入事件。然而,如果將手柄輸入的處理邏輯改為在一個獨立線程中進行,并在更高頻率下輪詢,那么就有可能出現事件計數大于 1 的情況。因此,為了讓未來的擴展更加靈活,API 設計中必須考慮這種可能性。

對于鍵盤輸入,目前的實現是通過 Windows 的消息隊列機制處理的。我們調用了 win32_process_pending_messages(),其中包括 win32_process_keyboard_message(),這就意味著鍵盤輸入是通過 Windows 的消息循環(message loop)異步傳遞的。這樣,如果系統在短時間內收到多個 WM_KEYDOWNWM_KEYUP 事件,它們會被保存在消息隊列中,并依次被處理,所以鍵盤的事件計數有可能超過 1。

我們還提到了,不清楚操作系統是否足夠頻繁地抓取鍵盤事件,從而導致事件堆積——但理論上,這確實是可能發生的,因此必須在設計上為這種情況做好準備。

鼠標輸入也可能存在類似機制,如果是通過 Windows 消息系統接收,那么在快速點擊或移動的情況下也可能產生多個輸入事件積壓。

總結來說:

  • 當前游戲手柄輸入由于僅每幀輪詢一次,事件數量幾乎不可能超過 1。
  • 如果未來對手柄輸入的輪詢頻率提高,那么事件數量可能會增加,API 需要具備處理多個事件的能力。
  • 鍵盤和鼠標輸入由于是通過 Windows 消息系統處理的,因此事件數量理論上可以超過 1,尤其在輸入高頻發生或處理不及時的情況下。
  • 為了保證游戲在不同輸入頻率和處理機制下依然運行良好,API 的設計必須具備良好的擴展性和容錯能力。
  • 應該為未來可能發生的高頻輸入事件處理打下基礎,避免因輸入事件頻率提升而造成程序邏輯錯誤或性能下降。

我們要做的是在當前保持簡潔的同時,預留接口和結構空間,使得未來在性能需求變化時,不會影響現有系統的穩定性和正確性。

關于圖形/Windows:為什么這么多游戲,尤其是 Source 引擎的游戲,Alt-Tab 處理不好?

很多游戲(尤其是使用 Source 引擎的游戲)在處理 Alt+Tab(切出游戲)操作時表現不佳,其核心問題主要與顯示分辨率的切換有關。

我們傾向于不更改系統的顯示分辨率。因為一旦改變顯示分辨率,就存在許多潛在的問題。用戶當前的顯示器設置能夠正常工作,證明顯示器在當前分辨率下是可用的。如果我們主動切換到其他分辨率,就可能進入一個顯示器不兼容的模式,導致畫面不同步、花屏、黑屏,甚至完全無法顯示。

大多數游戲在全屏運行時會嘗試切換分辨率到一個更小、更適合游戲性能的值。這種做法的初衷是降低 GPU 渲染負擔,提高幀率,尤其是在較老硬件或高性能要求的游戲中。但這會引發一系列問題:

  1. Alt+Tab 后系統自動恢復原始分辨率:Windows 會在用戶切出游戲時自動還原桌面分辨率,然后游戲再次獲取焦點時又需要切回游戲分辨率。這種來回切換本身就很不穩定,容易導致各種視覺和系統層面的異常。

  2. 桌面圖標錯亂、窗口位置被打亂:分辨率切換會讓系統重新布局桌面和窗口,造成使用體驗上的混亂。

  3. 顯示器不支持的分辨率模式:一些顯示器在特定刷新率或分辨率下會表現異常,例如花屏、偏色或者信號丟失,這對玩家來說非常糟糕。

我們更傾向于讓游戲在不改變顯示器分辨率的前提下運行。即使游戲內部渲染的分辨率較低,我們也會將其拉伸至顯示器原有分辨率進行顯示。雖然這樣會帶來一些額外的 GPU 負擔(主要是縮放處理),但現代硬件完全能夠承擔這部分開銷。

總體來說,這種處理方式有如下優點:

  • 更穩定的表現:Alt+Tab 不再引起分辨率切換,避免黑屏或系統異常。
  • 用戶體驗更好:不影響桌面布局,也避免因系統還原分辨率導致的視覺混亂。
  • 兼容性更強:避免和顯示器硬件的分辨率兼容性問題。

雖然不能說所有問題都是游戲本身的錯,Windows 對分辨率切換的支持長期以來就不理想。如果 Windows 能更好地管理分辨率模式切換,并保證游戲在全屏與窗口切換過程中狀態的穩定性,那么很多游戲就不必自己處理這些麻煩,自然也不會有那么多 Alt+Tab 的問題。

最終結論是:為確保更好的穩定性和用戶體驗,我們認為在運行游戲時盡量不要修改顯示器的分辨率,而是使用拉伸或其他圖像處理方式在現有分辨率下運行游戲內容。這比依賴系統頻繁切換模式要安全和高效得多。

把主窗口消息隊列移到另一個線程有什么好處嗎?抱歉,如果你已經實現或回答過這個問題,我還在趕進度

關于是否將主窗口消息隊列(Windows Message Queue)移動到另一個線程的問題,進行了詳細思考和分析,目前來看,認為沒有明顯的好處。

首先,從技術層面上講,Windows 消息機制本身有一定限制:窗口創建在哪個線程,消息就會投遞到那個線程的消息隊列。因此,無法簡單地通過代碼直接把已有窗口的消息處理轉移到另一個線程上。想要達到類似效果的辦法是,把游戲主邏輯啟動到另一個線程中去,而讓創建窗口的主線程專門用來處理消息循環(如 PeekMessage 這類操作)。但無論實現細節如何變化,本質上窗口消息仍然歸屬于最初創建它的線程。

針對這樣處理是否有實際好處,目前的觀點是看不到明顯的優勢。原因如下:

  • Windows 消息系統自帶隊列機制:很多需要頻繁獲取輸入(比如鍵盤輸入)的場景,如果自己輪詢硬件,需要自己負責頻繁拉取數據,以免丟失輸入。但在使用 Windows 消息隊列的情況下,Windows 系統自己負責捕獲并排隊這些輸入,只要及時處理隊列,不容易丟失任何事件。因此,不需要為避免漏掉消息而把消息處理挪到其他線程。

  • 某些消息合并特性符合預期:比如 WM_PAINT 這類繪制消息,Windows 在隊列里會自動合并重復消息,避免無意義的重復繪制請求。這種機制從應用角度來說是有益的,并不需要或想要處理所有原始產生的重復消息。

  • Windows 系統復雜且變化大:雖然長期有 Windows 編程經驗,但依然認為自己對 Windows 知識了解只是局部的,因此也不敢完全排除極少數情況下這樣做可能有意義。但至少就目前的經驗和常規應用場景來看,看不到特別需要這么做的理由。

  • 消息處理的延遲與積壓:如果處理消息的線程因為繁忙而不能及時清空消息隊列,確實可能出現延遲積壓的問題。但這種情況下,與其說是線程分配的問題,不如說是整體程序調度的問題,合理安排主線程負載和優化邏輯流程可能更合適。

總結來說,主窗口消息處理留在創建它的主線程上,通常是合理和高效的。沒有發現明顯需要把消息循環移到別的線程上的場景。消息隊列本身已經是為了避免丟失消息設計的,因此切換線程不會帶來什么優勢,反而可能增加系統復雜度,帶來更多同步和資源管理上的問題。當然,也不排除在極其特殊的應用需求下,可能存在細分優化空間,但那屬于非常小眾的情況了。

除了你已經展示過的教育用途,Windows 的 GDI 你還用過嗎?

基本上不使用 Windows 的 GDI(圖形設備接口)進行游戲開發,雖然在過去某些情況下偶爾用過,但在實際游戲項目中并不會依賴它。

現在的渲染路徑已經分為兩種:軟件渲染和硬件渲染。從現狀來看,實際上基本不會再用到 GDI。無論是走硬件渲染路徑,還是即使選擇軟件渲染路徑,最終也都會通過 OpenGL 渲染,所以根本不會真正去使用 GDI 繪制。唯一還會用到 GDI 的地方,就是在初始化 OpenGL 上下文時,創建必要的基本窗口和設備上下文,但真正渲染圖形時,全程都是通過 OpenGL完成的。

這種做法在現代 Windows 上開發任何面向性能的圖形應用時都是標準做法。GDI 本身并不是為高性能渲染設計的,真正需要性能的時候,通常只會把 GDI用來做一些必要的初始化工作,比如獲取硬件繪圖上下文(如 HDC),之后就直接交由 OpenGL 或 DirectX 接管。

除此之外,不會去使用 GDI 或者 GDI+,也不會去理會微軟近年來推出的各種圖形 API,比如 Direct2D、DirectComposition 或者其他什么名字很奇怪的新 API。對于高性能圖形開發,只關心能讓應用直接控制底層顯卡資源的接口,比如 OpenGL、DirectX、Vulkan 或 Mantle 之類。

總結下來就是,真正關心圖形性能的話,唯一合理的做法就是快速完成窗口創建和上下文初始化,然后直接進入 OpenGL、DirectX 或 Vulkan 這類底層圖形 API 的世界。其他 Windows 自帶的傳統高層圖形 API,在性能圖形開發中幾乎沒有存在的意義。

你怎么看 Vulkan?它會替代 D3D 嗎?

對 Vulkan 沒有特別喜歡的感覺,同樣也不喜歡 Direct3D 12。兩個 API 都不讓人覺得真正“好用”或“理想”,但這其實無關緊要,因為圖形 API 的普及從來就不是由它們的設計質量決定的。

回顧整個圖形 API 的發展歷史,很少有哪個 API 是因為“它真的寫得好”才成為主流的。實際決定哪個 API 成為主流的,是市場力量——誰在推動它,哪個廠商在支持它,哪個平臺在默認它。比如 OpenGL 和 Direct3D,之所以成為標準,并不是因為它們在設計上有多出色,而是因為它們是 Windows 平臺上為數不多能直接控制顯卡的通道,僅此而已。

所以 Vulkan 是否會取代 Direct3D 或 OpenGL,其實更多取決于平臺廠商和硬件廠商是否支持它,而不是它本身是否好用。換句話說,它能否勝出不取決于技術優勢,而取決于戰略推動。

微軟以前非常強硬地通過 Direct3D 來鎖定開發者到 Windows 平臺,并把這看作一個重要的戰略資源,他們曾經絕不會允許 Direct3D 被淘汰。甚至寧愿設法扼殺 Vulkan 的發展,也不愿看到自己控制的 API 被替代。

但近年來,微軟的戰略發生了變化。他們不再像過去那樣把“綁定 Windows 平臺”當作絕對目標,而是更在意像 Microsoft Store 這類平臺收益,更傾向于跨平臺、多系統運營的戰略。例如 WSL(Windows Subsystem for Linux) 和對 Linux 的各種支持都說明他們不再執著于“Windows 優先”。

這也意味著,微軟未來可能不會像從前那樣拼死保護 Direct3D。如果 Vulkan 被更多廠商采納并推動,微軟也可能不再干預。這使得 Vulkan 是否能成為主流圖形 API 的問題變得更復雜、更難預測。

至于 API 選擇和性能,像 OpenGL 中的 selection buffer、ray picking、color picking 等鼠標選擇方式各有優缺點。selection buffer 方法過于依賴舊的固定管線,已經不太適合現代 GPU;color picking 是一個可控性強、適用于現代渲染管線的技巧,但需要手動維護顏色編碼;ray picking 則在精確度上最佳,但實現復雜度較高,適合物理空間精度要求高的場景。

總的來說,現在圖形開發中真正有效的方式,往往不再依賴舊有的高層抽象 API,而是直接選擇 Vulkan、DirectX 12、Metal 等更底層、更靈活的現代圖形接口。最終使用什么,不是由開發者是否喜歡決定的,而是由平臺和生態圈的趨勢推動的。

選擇緩沖 vs 光線拾取 vs 顏色拾取,哪種適合鼠標拾取?

通常,我個人偏好使用射線拾取(ray picking),主要是因為我覺得做一次額外的渲染通道(render pass)會比較昂貴。雖然渲染通道可以提供較為精準的結果,但由于每次渲染都會涉及額外的計算和資源消耗,尤其是當場景較為復雜時,成本會顯著增加。而射線拾取通過直接計算射線與物體的交點,避免了多次渲染,能夠在性能和精度之間找到一個比較好的平衡點。因此,相比于額外的渲染通道,我更傾向于選擇射線拾取這種方式來處理物體選擇問題。

檢測鍵盤長按時,是否應該在輸入結構中放一個時間戳?

對于檢測按鍵按下的時長,可以通過在基礎設施中設置時間戳來實現。具體來說,當按鍵第一次按下時,啟動一個計時器,并在按鍵保持按下狀態時持續更新該計時器。這樣,就可以準確知道按鍵按下的時長。這個方法非常合理,能夠有效地跟蹤每次按鍵的持續時間,方便后續處理。

接下來你打算開發什么“功能”?

接下來要做的功能是完成調試代碼,預計下周開始著手。這包括調試圖表和相關的功能,目的是完善調試流程并提高可視化的效果,以便更好地分析和解決問題。

你認為 C++11、C++14、C++17 這些現代 C++ 命名法是為了掩蓋語言的混亂嗎?

關于現代合成命名法,如“C++11”、“C++14”、“C++17”等,觀點是,它們似乎凸顯了人們對于原始笑話的理解不夠。比如,“C++”這個命名其實就是個笑話,源于原本的增量運算符“++”,用在“C”上并不會直接改變C的值,而是會改變其它地方的內存值,而返回的依然是C。這就像是在說“C++”是在做“某個地方”的改進,但最終返回的卻還是原來的C。這種命名反映了這種幽默的復雜性。

然而,現代命名法并未完全理解這個笑話,甚至連原來的笑話本身也沒完全理解。例如,“C++11”這樣的命名其實并沒有保留原有的笑點,因為“C++”應該意味著“C”加上一些東西,但這種命名方式并未能與原來的含義保持一致。最起碼,應該是“C++11”或者類似的表達式才對,但實際的命名則沒有堅持這樣的邏輯。

總結來說,這種命名方式未能遵循其最初的幽默內涵,反而暴露了對原始含義的不理解,也許這正是對整個命名的諷刺所在。

是否值得為了賺錢而去學 JS 或 Python?

關于學習JavaScript和Python賺取金錢的問題,可以從不同的角度來看待。

首先,假設你學習這些編程語言是為了從事一些對社會有益的工作,而不是為了做一些可能對世界有害的事情,比如廣告公司等。這種情況下,學習JS和Python以賺取收入是完全合理的。畢竟,每個人都需要生活,賺錢養活自己是基本的需求。

然而,問題會在于如果你選擇的工作是幫助某些公司做一些有害的事情,比如通過垃圾廣告影響用戶生活或是一些侵害隱私的項目。那么,在這種情況下,就需要慎重考慮是否愿意為這些公司工作。每個人都有選擇的權利,尤其是在決定如何影響社會時,我們應該意識到自己對世界的影響以及我們愿意做出怎樣的決定。

作為程序員,很多人可能沒有意識到自己在做道德決策,但實際上我們每一個選擇都可能對社會產生影響。在決定是否接受一份工作時,應該先考慮這項工作是否會讓社會變得更好,還是在某種程度上剝削了別人,把資源從更重要的事情上轉移走。

因此,盡管為了生計去做一些工作是完全可以理解的,但如果這份工作涉及到從事一些不道德的事情,例如為一些剝削性的公司工作,那么就應該避免去做這種選擇。總的來說,程序員在選擇工作時應當考慮道德層面的責任,確保所從事的工作能為社會帶來正面的影響,而不是只為盈利而做出有害的選擇。

你對 SDL2 有什么看法?

關于SDL(Simple DirectMedia Layer),沒有太多的使用經驗,因此也沒有深入的看法。SDL是一種跨平臺的多媒體庫,通常用于游戲開發和其他需要圖形、聲音、輸入處理等功能的應用程序。雖然沒有使用過SDL,但通常這種庫可以幫助開發者簡化底層的硬件接口,提供更方便的工具來處理圖形顯示、音頻播放和輸入管理等。

不過,由于沒有實際使用過SDL,所以不能提供更詳細的體驗或具體的優缺點分析。

你打算使用什么技術做路徑查找?

關于路徑的技術,目前還沒有確定使用哪一種。可能會考慮使用像A*算法這樣的常見路徑尋找方法,但目前還不確定,具體的選擇可能會根據實際情況和需求來決定,可能會在后續開發過程中進一步探索并做出決策。

最近一直在做 JS,你覺得我下一步該做什么?對 JS 感到有點厭倦

如果對JavaScript感到厭倦,學習C語言是一個很好的選擇。C語言可以讓你更接近底層編程,它與JavaScript不同,JavaScript更多的是抽象的,虛擬的,而C語言則是更直接的,能夠操作內存并與硬件進行更緊密的交互。通過C語言編程,你可以獲得一種更具“實物感”的編程體驗,這是非常有價值的。

Vulkan 是非常低級的;這是由多個游戲引擎和硬件開發者要求的。可能有巨大的市場需要一個友好的 API 來封裝 Vulkan,幫助開發者避開這些復雜的細節。你不這么認為嗎?

關于Vulkan是否低級API的問題,首先,低級與否并不取決于API的抽象層次,復雜性與是否是“低級”無關。有些高層API也可以非常復雜。比如,聲音系統或者模塊化的API,可能在表面上看起來很簡單,但在實現時可能非常復雜。復雜性和抽象級別是兩個不同的概念。

對于Vulkan是否低級的看法,認為Vulkan是低級的實際上是誤解。Vulkan并不是一個低級API,盡管它比一些其他API(如OpenGL或DirectX)要靠近硬件。Vulkan其實是一個高層API,它與硬件的交互通過圖形驅動程序進行,而這些驅動程序會隱藏很多實現細節。因此,從當前的實現來看,Vulkan并沒有比OpenGL低級,它只是提供了一些更直接、更接近硬件狀態的接口,而這些接口在未來幾年里也不會比OpenGL更低級。

Vulkan的一個問題在于它并沒有明確的保證,它并不會直接向GPU發送命令,也不會直接編譯成硬件代碼。它的抽象程度與OpenGL或DirectX差別不大。Vulkan的真正低級API應該是一個幾乎沒有抽象層次的API,用戶需要直接管理內存、構建命令緩沖區等,這才是真正的“低級”API。

總的來說,Vulkan并不是一種低級API,它只是讓開發者有更多控制,但這并不意味著它比OpenGL或DirectX更底層。Vulkan與其說是低級API,不如說它是為了提高開發者控制權而設計的,但它的復雜性并未減少,反而是通過增加了更多的API調用,使得開發者需要面對更多的管理和配置工作。

你多關注最新的技術嗎?你認為學習最新的圖形 API 對于獨立開發來說是浪費時間嗎?

關于是否應該跟隨最新的技術,學習最新的工具和技術,這取決于具體的情況。如果你的目標是通過圖形上的特殊性來區分自己,那么跟進最新的技術是有意義的,因為如何在圖形上脫穎而出,如果不使用一些先進的技術,可能就很難實現這個目標。

然而,如果你的游戲更側重于某種獨特的藝術風格,而這種風格更多地依賴于藝術家的創作,而不是技術本身的能力,或者游戲的核心玩法更為重要,那么最新的技術可能并不是必需的。在這種情況下,追求技術的前沿可能不會對游戲的成功產生太大影響。

游戲開發中許多決策都有其利弊,選擇使用現有的引擎(如Unity或Unreal)可能是最合適的選擇,特別是當你的游戲需求與這些引擎非常契合時。作為開發者,最重要的不是僅僅掌握某些工具,而是能做出正確的技術選擇。這意味著,除了知道如何使用某個工具或引擎外,開發者還應當了解何時應該使用它,何時不應該使用它。

如果你僅僅依賴于Unity或者OpenGL這樣的工具,而沒有意識到可能有更合適的選擇,甚至無法評估其他工具或技術的優劣,那你會限制自己的能力,進而影響到項目的質量。一個好的開發者不僅要知道如何做,還要能夠做出最佳的技術決策。

此外,跟上新技術的步伐并不意味著要把所有的技術都學習一遍,但理解各種技術的存在以及它們的用途非常重要。如果不了解Vulkan這樣的圖形API,而在某些情況下它可能是更好的選擇,那么錯過它會是一個失誤。因此,了解其他技術并能判斷何時需要使用它們是每個開發者必備的技能。

總的來說,作為一名優秀的程序員,重要的是能做出明智的技術決策,無論是使用現有的引擎、選擇合適的API,還是在不同的開發需求之間做出權衡。

為什么程序員不應該為 Palantir 工作?

不建議程序員為某些公司工作,尤其是像“Palantir”這樣的公司,因為它們的核心業務是開發數據挖掘工具,用于進行大規模的監控和數據收集,特別是針對一些特定群體,如穆斯林等。這樣的公司通常會為政府和其他機構提供技術支持,這些機構可能會用這些工具進行各種侵犯隱私的活動,比如監控和數據分析,甚至可能助長不道德的行為。

從倫理角度來看,選擇為這樣的公司工作是不明智的,因為它可能助長了社會的不公和不道德行為。雖然作為程序員,工作時可能只關注技術和代碼的實現,但作為開發者,應該時刻意識到自己所做的工作可能對社會產生的深遠影響。就像科學家在參與曼哈頓計劃時需要考慮自己研究成果可能被用來制造毀滅性武器一樣,程序員也應該思考他們所做的技術是否可能被用來對社會造成傷害。因此,程序員在選擇工作時,應當審視自己的工作是否會助長負面的社會影響,確保自己所從事的工作能夠為世界帶來積極的變化,而不是加劇不公。

Jon 用的 C++ 特性比你的更多嗎?

John在編程時確實使用了比我們更多的C++特性。具體來說,他使用了更多的虛函數和一些C++的高級特性,雖然他不會頻繁使用私有數據或模板等特性,但偶爾也會使用一些。盡管如此,他在使用C++的復雜特性時仍然相對謹慎,并不會過度依賴這些特性。

John對C++的使用比我們更為復雜一些,但他并不會過于依賴這些功能,因為他并不完全喜歡C++的設計,尤其是虛函數等特性沒有完全滿足他的需求。這也是他自己開發語言(例如JAI)的原因之一。他希望能設計出一種語言,能夠更好地實現C++中的虛函數等功能,并且避免C++的一些局限性。因此,John開發的語言在某種程度上是為了克服C++在某些方面的不足,滿足他自己的編程需求。

你怎么看待新的 Snowdrop 游戲引擎?

對于新發布的Snowdrop游戲引擎,了解的信息并不多。雖然它被用在了《全境封鎖》這類游戲中,但實際上并沒有深入了解過這個引擎。盡管《全境封鎖》看起來畫面相當不錯,但也不能僅憑一款游戲來評價一個引擎。現在的計算機性能非常強大,幾乎所有的游戲引擎都能展現出相對不錯的效果,特別是如果藝術團隊的水平足夠高,最終效果就會顯得更加優秀。所以,僅憑游戲畫面并不能完全判斷一個引擎的好壞。
在這里插入圖片描述

考慮到人們聲稱 Vulkan 在性能上有差異,你打算使用 Vulkan 嗎?

對于是否在項目中使用Vulkan,主要取決于具體的需求。對于英偉達顯卡而言,OpenGL已經能夠提供非常好的性能,因此并不需要額外使用Vulkan。而對于AMD顯卡,情況則有所不同,因為它們的OpenGL驅動通常表現較差,因此可能需要使用Vulkan來提升性能。

然而,作為一個小型開發者,如果只是為了在AMD顯卡上提升性能,而必須投入大量精力去實現Vulkan的支持,那么這可能不是一個明智的決定。相比之下,專注于改善互動小說的表現或者其他更具創意性的方面,可能更為值得投資。

總的來說,Vulkan對于項目的支持并不會帶來實際的好處,因此目前沒有太大動力去支持它。

既然你從頭開始寫所有東西,為什么不使用 Go 這種語言呢?

關于為什么不使用Go語言,而選擇從頭開始編寫代碼,主要是因為Go語言是一個非常高層次的語言。這種語言雖然有很多方便的特性,但并不符合個人的偏好。高層次語言的特性雖然有很多,但它們的抽象和某些語言特性并不符合個人的需求,特別是對于那些更傾向于使用低層次特性的開發者來說。因此,盡管Go語言在很多應用中非常流行,但它并不適合每個開發者,特別是那些更喜歡具有更多控制和靈活性的開發者。

最近玩了什么游戲?感覺怎么樣?

最近并沒有玩太多游戲。主要玩了一些小游戲,但并沒有覺得一直玩游戲是個好主意。因此,最近沒有花很多時間在游戲上,反而更專注于其他事情。

你有過 POSIX Linux 系統調用(例如 fork())的經驗嗎?我需要創建一個小的進程調度器,但當我在 for 循環中使用 fork() 時,它會出問題。我需要創建 N 個進程,然后將它們保存到 FIFO 列表中并一個個執行

在討論使用Linux的fork系統調用時,提到如果在for循環中調用fork可能會出現問題。一般來說,fork調用在for循環內不應該導致問題。fork的作用是創建一個新進程,它會克隆當前進程并使用“寫時復制”(copy-on-write)機制管理內存。因此,調用fork時,父進程和子進程會共享內存,直到有一個進程修改該內存。父進程和子進程會分別返回不同的值,父進程返回子進程的PID,而子進程返回0。

如果在for循環中調用fork時遇到問題,可能是其他部分的代碼出了問題。重要的是要確保在進程中正確地判斷是父進程還是子進程,通常通過fork返回值來區分。子進程可以在適當的時候使用break語句退出循環,而父進程則可以繼續執行循環。

總的來說,forkfor循環中應該正常工作,除非代碼中有其他邏輯錯誤。

你想成為第一個玩 JAI 的人嗎?還是等它更成熟一點再玩?

是否愿意成為首批體驗新編程語言的人,主要取決于其發布時的時間安排。如果發布時恰好有空閑時間,并且不在忙碌的項目中,可能會考慮嘗試一下。但如果此時正忙于其他任務(例如正在進行的項目),那就可能沒有時間去深入研究新語言。因此,是否嘗試并不完全取決于對新語言的興趣,而更多取決于當時的時間安排。

如果真的要深入了解一個新編程語言,單純地快速瀏覽是不夠的。必須投入相當的時間進行實踐,因為僅憑外部的分析無法全面理解語言設計的決策。通過實際使用,可以更好地評估這些決策是否有效,是否能夠帶來實際的好處。很多時候,設計的決策背后有深思熟慮的原因,即便這些決策和原本的預期不同,最終的結果可能還是值得的。所以,要真正理解一個新語言,必須花時間去實踐、去體驗,而不僅僅是從表面看。

fork 是一個好的 API 嗎,還是壞的 API?

關于fork API是否是一個好的選擇,這取決于它的使用目標。如果目標是通過fork來創建一個新進程,那么它可能是一個合適的選擇。然而,如果目標是以其他方式創建更多進程,那么fork就不一定是最好的選擇,因為它的工作方式是復制當前進程的內存,這會引入不必要的開銷,尤其是在需要處理頁面復制的情況下,這種操作會變得非常沉重。

通常情況下,fork會復制進程的內存,這可能不符合所有的需求,特別是當只需要創建新進程而不需要復制現有進程的內存時。由于這種行為的代價較高,因此在某些場景下不推薦使用fork。然而,如果其設計的目的是為了實現進程克隆,那么它的確是一個合適的選擇。

總的來說,是否將fork視為一個好的API,取決于當初設計它時的具體目標和需求。如果目標是創建進程克隆且沒有其他更輕量級的替代方案,那么fork可能是合適的,但如果目標是更高效地創建進程,則fork就不太理想。

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

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

相關文章

2025.4.26總結

今天把馬良老師的《職場十二法則》看完后,感觸極大,這們課程就是一場職場啟蒙課。 雖然看過不少關于職場的書籍,但大多數是關于職場進階,方法方面的。并沒有解答“面對未來二三十年的職場生涯,我該怎么去看待自己的工…

路由器轉發規則設置方法步驟,內網服務器端口怎么讓異地連接訪問的實現

在路由器上設置端口轉發(Port Forwarding)可以將外部網絡流量引導到特定的局域網設備,這對于需要遠程訪問服務器、攝像頭、游戲主機等設備非常有用。 登錄路由器管理界面,添加端口轉發規則讓外網訪問內網的實現教程分享。以下是設…

Linux基礎命令總結

Linux系統命令 1. systemctl 1. 基本語法 systemctl start | stop | restart | status 服務名 2. 經驗技巧查看服務的方法:/usr/lib/systemd/system 3. 案例實操 (1)查看防火墻服務的狀態 systemctl status firewalld (2)停止防火墻服務 systemctl stop firewalld (…

【PVCodeNet】《Palm Vein Recognition Network Combining Transformer and CNN》

[1]吳凱,沈文忠,賈丁丁,等.融合Transformer和CNN的手掌靜脈識別網絡[J].計算機工程與應用,2023,59(24):98-109. 文章目錄 1、Background and Motivation2、Related Work3、Advantages / Contributions4、Method5、Experiments5.1、Datasets and Metrics5.2、Hyper-parameters5.…

《企業級 Java EE 架構設計精深實踐》內容詳解

《企業級 Java EE 架構設計精深實踐》內容詳解 1. 書籍核心主題 《企業級 Java EE 架構設計精深實踐》是一本深入探討Java EE 企業級架構設計的實戰指南,涵蓋分層架構、設計模式、分布式系統、微服務、性能優化、安全與監控等核心內容,結合 Java EE 技術…

Ragflow新建的知識庫完成后刷新卻沒有顯示,報錯MethodNotAllowed: 405 Method Not Allowed:

環境: Ragflow17.2 debian12.8 問題描述: Ragflow新建的知識庫完成后刷新卻沒有顯示,報錯MethodNotAllowed: 405 Method Not Allowed: The method is not allowed for the requested URL. 后臺日志: 2025-04-25 13:54:25,988 ERROR 235204 405 Method Not Allowed:…

使用 LangChain + Higress + Elasticsearch 構建 RAG 應用

RAG(Retrieval Augmented Generation,檢索增強生成) 是一種結合了信息檢索與生成式大語言模型(LLM)的技術。它的核心思想是:在生成模型輸出內容之前,先從外部知識庫或數據源中檢索相關信息&…

3dmax模型怎么處理3dtiles,制作制作B3DM格式文件

1咱們先打3dmax,或su或者其他軟件建模型 2記住面一定一定要少,面一定不能多,也不要是VR材質,可以用插件一鍵處理 3導出fbx 4使用cesium把fbx轉換 5這里可以坐標,因為要對地圖位置 6轉換出來了,3dtiles格式…

Vue2-指令語法

v-bind和v-model <a v-bind:href"url">筆記1</a> <a :href"url">筆記2</a><input type"text" v-model:value"name"/> <input type"text" v-model"name"/>data(){return {ur…

mac brew 無法找到php7.2 如何安裝php7.2

mac brew 無法找到php7.2 如何安裝php7.2 原因是升級過高版本的brew后已經不支持7.2了&#xff0c;但可以通過第三方工具來安裝 brew tap shivammathur/php brew install shivammathur/php/php7.2標題安裝完成后會提示以下信息&#xff1a; The php.ini and php-fpm.ini fil…

想要從視頻中提取背景音樂怎么搞?其實視頻提取音頻非常簡單

在日常生活中&#xff0c;我們經常遇到這樣的情況&#xff1a;有一段非常精彩的視頻&#xff0c;而其中的背景音樂或對話正是你所需要的。這時&#xff0c;如果能將這段音頻單獨提取出來&#xff0c;就可以方便地在其他場合使用了。通過一些專業的軟件工具&#xff0c;如 Video…

第十六屆藍橋杯網安初賽wp

解題列表 根據提示一步一步走&#xff0c;經過猜測&#xff0c;測試出app.py 經過仔細研讀代碼&#xff0c;找到密鑰 編寫python代碼拿到flag key secret_key9828 flagd9d1c4d9e0d6c29e9aad71696565d99bc8d892a8979ec7a69b9a6868a095c8d89dac91d19ba9716f63b5 newbytearray(…

【leetcode100】單詞拆分

1、題目描述 給你一個字符串 s 和一個字符串列表 wordDict 作為字典。如果可以利用字典中出現的一個或多個單詞拼接出 s 則返回 true。 注意&#xff1a;不要求字典中出現的單詞全部都使用&#xff0c;并且字典中的單詞可以重復使用。 示例 1&#xff1a; 輸入: s "l…

機器人項目管理新風口:如何高效推動智能機器人研發?

在2025年政府工作報告中&#xff0c;“智能機器人”首次被正式納入國家發展戰略關鍵詞。從蛇年春晚的秧歌舞機器人驚艷亮相&#xff0c;到全球首個人形機器人馬拉松的熱議&#xff0c;智能機器人不僅成為科技前沿的焦點&#xff0c;也為產業升級注入了新動能。而在熱潮背后&…

k8s學習記錄(四):節點親和性

一、前言 在上一篇文章里&#xff0c;我們了解了 Pod 中的nodeName和nodeSelector這兩個屬性&#xff0c;通過它們能夠指定 Pod 調度到哪個 Node 上。今天&#xff0c;我們將進一步深入探索 Pod 相關知識。這部分內容不僅信息量較大&#xff0c;理解起來也有一定難度&#xff0…

NeRF:原理 + 實現 + 實踐全流程配置+數據集測試【Ubuntu20.04 】【2025最新版】

一、引言 從三維建模、虛擬現實到電影級渲染&#xff0c;真實感建模一直是計算機視覺和圖形學的核心目標。 在傳統方法中&#xff0c;我們往往依賴&#xff1a; 多視角立體&#xff08;MVS&#xff09;點云重建 網格擬合顯式建模&#xff08;如多邊形、體素、TSDF&#xff0…

ASP.NET MVC? 入門指南三

16. 安全性 16.1 身份驗證和授權 身份驗證&#xff1a;確認用戶的身份。ASP.NET MVC 支持多種身份驗證方式&#xff0c;如表單身份驗證、Windows 身份驗證和 OAuth 等。 表單身份驗證&#xff1a;用戶通過輸入用戶名和密碼登錄&#xff0c;服務器驗證后頒發一個身份驗證票證&…

佳博票據和標簽打印:Web網頁端與打印機通信 | iOS

文章目錄 引言I Web網頁端與打印機通信webSDK(包含示例頁)打印測試II iOS與佳博打印機通信引言 佳博工具下載ESC是票據打印指令,TSC是標簽打印指令 工業打印機:佳博GP-H430F工業機標簽條碼打印機物流快遞電子面單條碼機碳帶機 應用場景:打印商品價格標簽、打印交易小票 I…

c語言初識

學c注意事項 我寫了很多服務器的代碼&#xff0c;我怕有些人看不懂所以就寫了這篇入門篇。 學習c語言要多動手&#xff0c;多練習&#xff0c;其實語法就幾個,你了解了就會寫出自己想要的代碼&#xff0c;你不要怕不會寫不出程序&#xff0c;因為大部分代碼都有人寫好&#xf…

請求參數、路徑參數、查詢參數、Spring MVC/FeignClient請求相關注解梳理

目錄 1 請求分類1.1 URL參數--查詢參數1.2 URL參數--路徑參數 2 請求相關注解2.1 RequestParam--查詢參數2.2 PathVariable--路徑參數2.3 RequestBody2.4 Param & RequestLine2.5 SpringMVC請求參數注解用在FeignClient里 使用SpringMVC處理http請求或使用FeignClient進行請…