Skip to content

Commit

Permalink
doom: separate game logic & drawing into two tasks with double buffering
Browse files Browse the repository at this point in the history
  • Loading branch information
and3rson committed Mar 10, 2024
1 parent addc104 commit b97fed7
Showing 1 changed file with 99 additions and 72 deletions.
171 changes: 99 additions & 72 deletions firmware/doom/src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,19 @@ uint16_t keyqueueRead = 0;
uint16_t keyqueueWrite = 0;
uint64_t lastRender = 0;

SemaphoreHandle_t inputMutex;
SemaphoreHandle_t backBufferMutex;
EventGroupHandle_t backBufferEvent;
TaskHandle_t gameTaskHandle;
TaskHandle_t drawTaskHandle;

uint32_t* backBuffer = NULL;

void gameTask(void* arg);
void drawTask(void* arg);

void buttonHandler(lilka::Button button, bool pressed) {
xSemaphoreTake(inputMutex, portMAX_DELAY);
doomkey_t* key = &keyqueue[keyqueueWrite];
switch (button) {
case lilka::Button::UP:
Expand Down Expand Up @@ -51,17 +63,26 @@ void buttonHandler(lilka::Button button, bool pressed) {
break;
default:
// TODO: Log warning?
xSemaphoreGive(inputMutex);
return;
}

key->pressed = pressed;
keyqueueWrite = (keyqueueWrite + 1) % 16;
xSemaphoreGive(inputMutex);
}

void setup() {
lilka::display.setSplash(doom_splash);
lilka::begin();

inputMutex = xSemaphoreCreateMutex();
xSemaphoreGive(inputMutex);
backBufferMutex = xSemaphoreCreateMutex();
xSemaphoreGive(backBufferMutex);
backBufferEvent = xEventGroupCreate();
xEventGroupClearBits(backBufferEvent, 1);

int argc = 3;
char arg[] = "doomgeneric";
char arg2[] = "-iwad";
Expand Down Expand Up @@ -98,91 +119,92 @@ void setup() {
}
char* argv[3] = {arg, arg2, arg3};

DG_printf("Doomgeneric starting, wad file: %s\n", arg3);
DG_printf("Doomgeneric starting, WAD file: %s\n", arg3);

D_AllocBuffers();
// Back buffer must be allocated before doomgeneric_Create since it calls DG_DrawFrame
backBuffer = (uint32_t*)malloc(DOOMGENERIC_RESX * DOOMGENERIC_RESY * 4);
doomgeneric_Create(argc, argv);
if (backBuffer == NULL) {
DG_printf("Failed to allocate back buffer\n");
esp_restart();
}

lilka::controller.setGlobalHandler(buttonHandler);

// while (1) {
// doomgeneric_Tick();
// }

Serial.println("Ready, starting tasks");

xTaskCreatePinnedToCore(gameTask, "gameTask", 32768, NULL, 1, &gameTaskHandle, 0);
xTaskCreatePinnedToCore(drawTask, "drawTask", 32768, NULL, 1, &drawTaskHandle, 1);

while (1) {
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
// D_FreeBuffers(); // TODO - never reached
}

void gameTask(void* arg) {
while (1) {
doomgeneric_Tick();
taskYIELD();
}
}

void drawTask(void* arg) {
while (1) {
// Wait for buffer to be ready
xEventGroupWaitBits(backBufferEvent, 1, pdTRUE, pdTRUE, portMAX_DELAY);
xSemaphoreTake(backBufferMutex, portMAX_DELAY);

// Calculate FPS
uint64_t now = millis();
uint64_t delta = now - lastRender;
lastRender = now;
lilka::display.startWrite();
lilka::display.writeAddrWindow(0, 20, 280, 175);
uint16_t row[280];
for (int y = 0; y < 175; y++) {
for (int x = 0; x < 280; x++) {
int yy = y * 8 / 7;
int xx = x * 8 / 7;
uint32_t pixel = backBuffer[yy * 320 + xx];
uint8_t r = (pixel >> 16) & 0xff;
uint8_t g = (pixel >> 8) & 0xff;
uint8_t b = pixel & 0xff;
row[x] = lilka::display.color565(r, g, b);
}
lilka::display.writePixels(row, 280);
}
lilka::display.endWrite();
lilka::display.setTextBound(0, 0, LILKA_DISPLAY_WIDTH, LILKA_DISPLAY_HEIGHT);
lilka::display.setCursor(32, 0);
lilka::display.setTextColor(lilka::display.color565(255, 255, 255), lilka::display.color565(0, 0, 0));
lilka::display.setFont(u8g2_font_10x20_t_cyrillic);
lilka::display.print(" FPS: ");
lilka::display.print(1000 / delta);
lilka::display.print(" ");

xSemaphoreGive(backBufferMutex);
taskYIELD();
}
D_FreeBuffers(); // TODO - never reached
}

extern "C" void DG_Init() {
}

extern "C" void DG_DrawFrame() {
// Calculate FPS
uint64_t now = millis();
uint64_t delta = now - lastRender;
lastRender = now;
lilka::display.startWrite();
// lilka::display.writeAddrWindow(0, 65, 240, 150);
// Цей код - застарілий. Я адаптував Doom Generic для роботи з 240x150 за замовчуванням.
// Convert 640x400 to 240x150
// for (int y = 0; y < 150; y++) {
// for (int x = 0; x < 240; x++) {
// int yy = y * 8 / 3;
// int xx = x * 8 / 3;
// uint32_t pixel = DG_ScreenBuffer[yy * 640 + xx];
// uint8_t r = (pixel >> 16) & 0xff;
// uint8_t g = (pixel >> 8) & 0xff;
// uint8_t b = pixel & 0xff;
// row[x] = lilka::display.color565(r, g, b);
// }
// lilka::display.writePixels(row, 240);
// }
// Нова версія:
// for (int y = 0; y < 150; y++) {
// for (int x = 0; x < 240; x++) {
// uint32_t pixel = DG_ScreenBuffer[y * 240 + x];
// uint8_t r = (pixel >> 16) & 0xff;
// uint8_t g = (pixel >> 8) & 0xff;
// uint8_t b = pixel & 0xff;
// row[x] = lilka::display.color565(r, g, b);
// }
// lilka::display.writePixels(row, 240);
// }
// Convert 320x200 to 240x150
// for (int y = 0; y < 150; y++) {
// for (int x = 0; x < 240; x++) {
// // Map 240x150 to 320x200
// int yy = y * 4 / 3;
// int xx = x * 4 / 3;
// uint32_t pixel = DG_ScreenBuffer[yy * 320 + xx];
// uint8_t r = (pixel >> 16) & 0xff;
// uint8_t g = (pixel >> 8) & 0xff;
// uint8_t b = pixel & 0xff;
// row[x] = lilka::display.color565(r, g, b);
// }
// lilka::display.writePixels(row, 240);
// }
// Convert 320x200 to 280x175 by skipping every 8th pixel
lilka::display.writeAddrWindow(0, 20, 280, 175);
uint16_t row[280];
for (int y = 0; y < 175; y++) {
for (int x = 0; x < 280; x++) {
int yy = y * 8 / 7;
int xx = x * 8 / 7;
uint32_t pixel = DG_ScreenBuffer[yy * 320 + xx];
uint8_t r = (pixel >> 16) & 0xff;
uint8_t g = (pixel >> 8) & 0xff;
uint8_t b = pixel & 0xff;
row[x] = lilka::display.color565(r, g, b);
}
lilka::display.writePixels(row, 280);
}
lilka::display.endWrite();
lilka::display.setTextBound(0, 0, 240, 280);
lilka::display.setCursor(0, 32);
lilka::display.setTextColor(lilka::display.color565(255, 255, 255), lilka::display.color565(0, 0, 0));
lilka::display.setFont(u8g2_font_10x20_t_cyrillic);
lilka::display.print(" FPS: ");
lilka::display.print(1000 / delta);
lilka::display.print(" ");
// Frame is ready.
// Acquire back buffer, swap buffers and set event
xSemaphoreTake(backBufferMutex, portMAX_DELAY);
uint32_t* temp = backBuffer;
backBuffer = DG_ScreenBuffer;
DG_ScreenBuffer = temp;
xEventGroupSetBits(backBufferEvent, 1);
xSemaphoreGive(backBufferMutex);
}

extern "C" void DG_SetWindowTitle(const char* title) {
Expand All @@ -199,15 +221,20 @@ extern "C" uint32_t DG_GetTicksMs() {
}

extern "C" int DG_GetKey(int* pressed, unsigned char* doomKey) {
xSemaphoreTake(inputMutex, portMAX_DELAY);
int ret;
if (keyqueueRead != keyqueueWrite) {
doomkey_t* key = &keyqueue[keyqueueRead];
printf("Got key: %d, pressed: %d\n", key->key, key->pressed);
*pressed = key->pressed;
*doomKey = key->key;
keyqueueRead = (keyqueueRead + 1) % 16;
return 1;
ret = 1;
} else {
ret = 0;
}
return 0;
xSemaphoreGive(inputMutex);
return ret;
}

bool hadNewLine = true;
Expand Down

0 comments on commit b97fed7

Please sign in to comment.