---
# System prepended metadata

title: 2020q3 Homework5 (render)
tags: [sysprog2020]

---

# 2020q3 Homework5 (render)
contributed by < `OscarShiang` >

## 測試環境
```shell
$ cat /etc/os-release 
NAME="Ubuntu"
VERSION="20.04 LTS (Focal Fossa)"
$ cat /proc/version
Linux version 5.4.0-47-generic (buildd@lcy01-amd64-014) 
(gcc version 9.3.0 (Ubuntu 9.3.0-10ubuntu2))
```

## 作業要求
- [ ] 修正浮點數和定點數算繪程式展現的缺失，並提出改進 precision  及 accuracy 的方式
- [x] 輸出算繪過程的 [frame rate](https://en.wikipedia.org/wiki/Frame_rate)，日後當我們進一步提升算繪程式的效率時，這會是效能評比的方式之一
- [x] 利用 [tools/precalculator.cpp](https://github.com/sysprog21/raycaster/blob/master/tools/precalculator.cpp) 產生運算表格，修改相關程式碼，使得程式碼在編譯時期才去產生運算表格，後者以標頭檔案 (generated header) 的形式存在並編譯進入主程式。換言之，檔案 [raycaster_tables.h](https://github.com/sysprog21/raycaster/blob/master/raycaster_tables.h) 應自 repository 移除，改用編譯時期產生
- [ ] 解說現有 fixed-point 實作機制，並探討前述表格產生的機制，需要提及其中的考量點
- [ ] 參照 [C 語言：物件導向程式設計篇](https://hackmd.io/@sysprog/c-oop)，透過建立共用介面 (interface) 的手法，將 [raycaster](https://github.com/sysprog21/raycaster) 以 C99/C11 (或 gnu99/gnu11) 重寫，允許在執行時期載入 fixed-point 和 floating-point 為基礎的 renderer

## 輸出算繪過程的 frame rate

根據 [Frame rate - Wikipedia] 的描述

> Frame rate (expressed in frames per second or FPS) is the frequency (rate) at which consecutive images called frames appear on a display.

可以知道 FPS 就是每秒我們更新了多少次遊戲畫面。

而對應到 `raycaster` 中就是在一秒內，`main.cpp` 中的 game loop 跑了幾次。

所以我們可以先看到 `main.cpp:main` 函式中的實作：

```cpp
int main(int argc, char *args[])
{

    <...>
    
            while (!isExiting) {
                floatRenderer.TraceFrame(&game, floatBuffer);
                fixedRenderer.TraceFrame(&game, fixedBuffer);

                DrawBuffer(sdlRenderer, fixedTexture, fixedBuffer, 0);
                DrawBuffer(sdlRenderer, floatTexture, floatBuffer,
                           SCREEN_WIDTH + 1);

                SDL_RenderPresent(sdlRenderer);

                if (SDL_PollEvent(&event)) {
                    isExiting =
                        ProcessEvent(event, &moveDirection, &rotateDirection);
                }
                const auto nextCounter = SDL_GetPerformanceCounter();
                const auto seconds = (nextCounter - tickCounter) /
                                     static_cast<float>(tickFrequency);
                tickCounter = nextCounter;
                game.Move(moveDirection, rotateDirection, seconds);
            }

    <...>
}
```

在這個 while 迴圈之中，我們可以看到 `raycaster` 利用這個迴圈重複地進行算繪，並將 floating-point 與 fixed-point 的 buffer 利用 `SDL_RenderPresent` 顯示在視窗上，因此從這邊我們可以判斷這個迴圈就是 game loop

根據 SDL Wiki 上的說明，我們可以知道在這邊使用到的 `SDL_GetPerformanceCounter` 是用來取得 SDL 內建的 high resolution counter 的數值，而我們可以透過這個數值差來算出兩次 game loop 之間相差多久。

而在上述程式碼中可以看到在取得兩個 frame 之間的 counter 差值後，我們是透過 `tickFrequency` 來算出時間差的，而這個部分可以回推到 `main.cpp:93` 的部分 

```cpp
const static auto tickFrequency = SDL_GetPerformanceFrequency();
```

`SDL_GetPerformanceFrequency` 這個函式在 SDL Wiki 中的描述是

> Returns a platform-specific count per second.

因此我們可以透過這個函式得知當前運行平台每秒對應到多少 count，透過這樣我們就可以從 `(nextCounter - tickCounter) /
                                     static_cast<float>(tickFrequency)` 計算出產生這個 frame 所花費的時間。並從這邊推得 FPS 的數值。

在 FPS 的實作中，我使用兩個變數作為 counter，分別計算從上次更新 FPS 到目前的秒數以及產生的 frame 的數量

```cpp
frame_count++;
sec_counter += seconds;
```

當 `sec_counter` 大於 1 時，將 `frame_counter` 中的數值作為 FPS 輸出，並清空

```cpp
if (sec_counter >= 1.0f) {
    cout << "FPS = " << frame_count;
    sec_counter = 0;
    frame_count = 0;
}
```

得到的執行結果如下：

```shell
$ ./main
FPS = 44
FPS = 61
FPS = 60
FPS = 61
FPS = 61
FPS = 61

<...>
```

為了方便在測試的過程中，查看 FPS 的數值，我將這個結果改以顯示在遊戲視窗的標題中

所以我們需要將程式碼進行以下的修改

```diff
                frame_count++;
                sec_counter += seconds;
                if (sec_counter >= 1.0f) {
-                   cout << "FPS = " << frame_count;
+                   stringstream title;
+                   title << GAME_TITLE "   FPS = " << frame_count;
+                   SDL_SetWindowTitle(sdlWindow, title.str().c_str());
                    sec_counter = 0;
                    frame_count = 0;
```

而結果呈現如下紅框所示

![](https://i.imgur.com/0PLC9Qs.png)



## 利用 tools/precalculator.cpp 產生運算表格

因為 `raycaster_table.h` 中的數值是固定的，所以我們可以在編譯時期才產生

為了將在 `RayCasterPrecalculator::Precalculate` 計算出來的表格輸出到檔案中，我們需要修改其實作的回傳值

```diff
 #pragma once

+#include <string>

 class RayCasterPrecalculator
 {
 public:
     RayCasterPrecalculator();
     ~RayCasterPrecalculator();
-    static void Precalculate();
+    static std::string Precalculate();
 };
```

接著實作 `table_generator` 將結果輸出到檔案中

```cpp
int main(int argc, char *argv[])
{
    if (argc != 2) {
        cout << "[ERR] Output filename is needed" << endl;
        exit(-1);
    }

    string filename(argv[1]);

    RayCasterPrecalculator calculator;
    string data = calculator.Precalculate();

    generate_table(filename, data);

    return 0;
}
```

`generate_table` 的實作如下

```cpp
void generate_table(string filename, string data)
{
    fstream out(filename, ios::out);
    string macro_id;
    for (int i = 0; i < filename.length(); i++) {
    }

    char marcro_id[filename.length() + 1];
    for (int i = 0; i < filename.length(); i++) {
        switch (filename[i]) {
        case '.':
            marcro_id[i] = '_';
            break;
        default:
            marcro_id[i] = toupper(filename[i]);
        }
    }
    marcro_id[filename.length()] = '\0';
    string macro(marcro_id);

    /* Add dependencies */
    out << "#include \"raycaster.h\""
        << "\n\n";

    /* Add protect marco */
    out << "#ifndef " << macro << '\n';
    out << "#define " << macro << "\n\n";

    out << data;

    out << "#endif /* " << macro << " */" << '\n';

    out.close();
}
```

測試其產生的結果是否正確

```shell
$ ./table_generator raycaster_tables.h
$ cat raycaster_tables.h
#include "raycaster.h"

#ifndef RAYCASTER_TABLES_H
#define RAYCASTER_TABLES_H

const uint16_t LOOKUP_TBL g_tan[256] = {0,1,3,4,6,7,9,11,12,14,15,17,18,20,22,23,25,26,28,29,31,33,34,36,37,39,41,42,44,46,47,49,50,52,54,55,57,59,60,62,64,65,67,69,70,72,74,75,77,79,81,82,84,86,88,89,91,93,95,96,98,100,102,104,106,107,109,111,113,115,117,119,121,123,124,126,128,130,132,134,136,138,140,142,145,147,149,151,153,155,157,159,162,164,166,168,171,173,175,177,180,182,185,187,189,192,194,197,199,202,204,207,210,212,215,218,220,223,226,229,232,234,237,240,243,246,249,252,255,259,262,265,268,272,275,278,282,285,289,293,296,300,304,308,311,315,319,323,328,332,336,340,345,349,354,358,363,368,373,378,383,388,393,398,404,409,415,421,427,433,439,445,451,458,465,471,478,486,493,500,508,516,524,532,541,549,558,568,577,587,597,607,618,628,640,651,663,675,688,701,715,729,744,759,774,791,808,825,843,862,882,903,925,947,971,996,1022,1049,1077,1108,1140,1173,1209,1246,1286,1329,1374,1423,1475,1531,1591,1655,1725,1801,1884,1975,2075,2185,2308,2445,2599,2773,2972,3202,3470,3787,4166,4631,5210,5956,6950,8341,10428,13905,20859,41720};

const uint16_t LOOKUP_TBL g_cotan[256] = {0,41720,20859,13905,10428,8341,6950,5956,5210,4631,4166,3787,3470,3202,2972,2773,2599,2445,2308,2185,2075,1975,1884,1801,1725,1655,1591,1531,1475,1423,1374,1329,1286,1246,1209,1173,1140,1108,1077,1049,1022,996,971,947,925,903,882,862,843,825,808,791,774,759,744,729,715,701,688,675,663,651,640,628,618,607,597,587,577,568,558,549,541,532,524,516,508,500,493,486,478,471,465,458,451,445,439,433,427,421,415,409,404,398,393,388,383,378,373,368,363,358,354,349,345,340,336,332,328,323,319,315,311,308,304,300,296,293,289,285,282,278,275,272,268,265,262,259,256,252,249,246,243,240,237,234,232,229,226,223,220,218,215,212,210,207,204,202,199,197,194,192,189,187,185,182,180,177,175,173,171,168,166,164,162,159,157,155,153,151,149,147,145,142,140,138,136,134,132,130,128,126,124,123,121,119,117,115,113,111,109,107,106,104,102,100,98,96,95,93,91,89,88,86,84,82,81,79,77,75,74,72,70,69,67,65,64,62,60,59,57,55,54,52,50,49,47,46,44,42,41,39,37,36,34,33,31,29,28,26,25,23,22,20,18,17,15,14,12,11,9,7,6,4,3,1};

<...>

#endif /* RAYCASTER_TABLES_H */
```

並將 `table_generator` 整合到 `Makefile` 中

```diff
diff --git a/Makefile b/Makefile
index e554d48..67d0780 100644
--- a/Makefile
+++ b/Makefile
@@ -1,7 +1,12 @@
 BIN = main
+TBL_GEN = table_generator

 CXXFLAGS = -std=c++11 -O2 -Wall -g

+LOOKUP_TBL := raycaster_tables.h
+
+TOOLS_DIR := tools
+
 # SDL
 CXXFLAGS += `sdl2-config --cflags`
 LDFLAGS += `sdl2-config --libs`
@@ -18,7 +23,7 @@ endif
 GIT_HOOKS := .git/hooks/applied
 .PHONY: all clean

-all: $(GIT_HOOKS) $(BIN)
+all: $(GIT_HOOKS) $(LOOKUP_TBL) $(BIN)

 $(GIT_HOOKS):
        @scripts/install-git-hooks
@@ -32,14 +37,29 @@ OBJS := \
        main.o
 deps := $(OBJS:%.o=.%.o.d)

+TBL_GEN_OBJS := \
+       tools/precalculator.o \
+       tools/table_generator.o
+deps := $(TBL_GEN_OBJS:%.o=.%.o.d)
+
 %.o: %.cpp
+       @mkdir -p .$(TOOLS_DIR)
        $(VECHO) "  CXX\t$@\n"
        $(Q)$(CXX) -o $@ $(CXXFLAGS) -c -MMD -MF .$@.d $<

 $(BIN): $(OBJS)
-       $(Q)$(CXX)  -o $@ $^ $(LDFLAGS)
+       $(Q)$(CXX) -o $@ $^ $(LDFLAGS)
+
+$(TBL_GEN): $(TBL_GEN_OBJS)
+       $(VECHO) "  CXX\t$@\n"
+       $(Q)$(CXX) -o $@ $^
+
+$(LOOKUP_TBL): $(TBL_GEN)
+       $(VECHO) "  GEN\t$@\n"
+       $(Q)eval "./$^ $@"

 clean:
-       $(RM) $(BIN) $(OBJS) $(deps)
+       $(RM) $(BIN) $(OBJS) $(TBL_GEN) $(TBL_GEN_OBJS) $(deps)
+       rm -rf .$(TOOLS_DIR)

 -include $(deps)
```

在 `all` 這個 target 中調整順序就可以確保在編譯 raycaster 前會先將 `raycaster_tables.h` 這個表格產生出來後才開始進行編譯。

測試的結果如下

```shell
$ make
  CXX	tools/precalculator.o
  CXX	tools/table_generator.o
  CXX	table_generator
  GEN	raycaster_tables.h
  CXX	game.o
  CXX	raycaster_fixed.o
  CXX	raycaster_float.o
  CXX	renderer.o
  CXX	main.o
```

## 參考資料


###### tags: `sysprog2020`