將 Z80 推向極限:在 ZX Spectrum 上進行即時 3D 渲染

ZX Spectrum 48K 是 1980 年代家用電腦的代表作,但它從未被設計用於 3D 圖形。憑藉 Z80 CPU 和僅有的 48KB RAM,硬體限制極其巨大。然而,開發者 Thanassis (ttsiodras) 最近的一個專案展示了透過巧妙的數學捷徑和激進的組合語言優化,即時 3D 點雲渲染不僅是可能的,而且流暢得令人驚訝。

這個專案是「無用嘗試」的典範——這種工程實踐旨在將硬體推向絕對極限,以探索各種可能性。透過將一個 3D 點雲渲染器從 ATmega328P 移植到 Z80,作者強調了高階 C 語言代碼與在復古矽晶片上手寫優化的組合語言之間的顯著差異。

效能差距:C 語言 vs. 組合語言

該專案最令人震驚的發現之一是 Z80 C 編譯器與手寫組合語言相比的低效率。當使用 C 語言實作 3D 投影迴圈時,渲染器的幀率約為 6.2 FPS。透過將核心邏輯改寫為 Z80 組合語言,作者成功將其提升至 14.0 FPS

這種加速是透過以下幾種底層優化實現的:

  • 暫存器管理: 作者對 Z80 暫存器集的利用效率遠高於編譯器。
  • 倒數查找表: 除法在 Z80 上是昂貴的操作。作者使用倒數查找表將昂貴的除法替換為乘法。
  • 基於頁面的查找: 為了進一步加速記憶體存取,作者實作了「基於頁面」的查找,將查找表的偏移量高位元組載入到 H 暫存器,並將索引載入到 L 暫存器,以便從 (HL) 讀取。

對於追求更高效能的人,作者嘗試了預計算分支。透過預先計算整個路徑和螢幕記憶體寫入——本質上將渲染器變成一個播放系統——幀率飆升至 40 FPS。這個版本預先計算了目標像素的視訊 RAM 位置和像素偏移量,將內迴圈簡化為簡單的記憶體讀取與寫入。

極簡投影的數學原理

為了在 3.5MHz 的處理器上實現可行的 3D 渲染,作者完全避開了複雜的旋轉矩陣和浮點數運算。相反,該專案使用了一種簡化的投影模型,其中相機環繞物體旋轉,而非物體本身旋轉。

投影方程式

核心執行時邏輯依賴於三個簡單的方程式:

wxnew = X' - mcos
y = 96 - Z' / wxnew
x = 128 + (Y' + msin) / wxnew

在此模型中,96128 代表 Spectrum 的 256x192 螢幕中心點。為了避免浮點數運算,所有原始數據在建置流程中透過因子 $S = 8960$ 進行預縮放,並轉換為整數。

為速度進行預處理

效能的提升不僅來自於執行時迴圈,還來自於透過 points_gen.py 進行的建置流程優化:

  1. 軸向交換: 儲存順序被更改為 $[X, Z, Y]$。這使得 CPU 可以先計算深度和螢幕-Y 座標,從而能夠跳過任何落在螢幕垂直邊界之外的點的螢幕-X 座標計算。
  2. 定點數空間: 座標被轉換為「螢幕就緒」空間,以最小化執行時運算。
  3. 非對稱縮放: 相機環繞的弦與餘弦值被進行了不同的縮放。mcos(控制相機深度)使用適度的偏移,而 msin(控制水平擺動)則進行了顯著更高的縮放,以確保除法後的像素移動具有意義。

社群的工程實踐見解

該專案激發了關於 8 位元開發本質的討論。一個反覆出現的主題是工具在生產力方面的角色。正如社群成員 @flohofwoe 所指出的,80 年代開發速度慢的觀感並不一定是因為組合語言本身,還是因為缺乏現代工具。

「使用好的巨集組合語言器,你其實已經非常接近像 C 語言這樣的高階語言了,而不是機器碼...透過使用模擬器進行快速的『編輯-編譯-除錯』迴圈...你獲得的生產力幾乎與在『正式』的高階程式語言中工作一樣。」

此外,社群也分享了針對 Z80 優化的技術提示,例如避免使用 IXIY 暫存器(這兩者速度較慢),以及利用 256 位元組對齊的查找表,透過僅操作暫存器對的低位元組來簡化索引操作。

結論

從 C 語言的 6.2 FPS 到預計算組合語言的 40 FPS 的過程,凸顯了復古電腦的核心真理:硬體是極限,但軟體是槓桿。透過將運算負擔從執行時轉轉移到建置流程,並利用 Z80 架構的特定特性,一台 1982 年的機器仍然可以用其能力讓我們感到驚喜。

Sources