import old blogposts & update projects

This commit is contained in:
Kevin Trogant 2023-01-09 12:52:26 +01:00
parent 73922e9056
commit 562cb9e890
7 changed files with 270 additions and 7 deletions

View File

@ -14,7 +14,8 @@ enableRobotsTXT = true
{name = "twitter", url = "https://twitter.com/GreenFoxLight"}, {name = "twitter", url = "https://twitter.com/GreenFoxLight"},
{name = "linkedin", url = "https://www.linkedin.com/in/kevin-trogant-232124224/"} {name = "linkedin", url = "https://www.linkedin.com/in/kevin-trogant-232124224/"}
] ]
description = "Programming | 3D Graphics | Sailing | Tabletop RPGs" description = "Game Development | 3D Graphics"
# | Sailing | Tabletop RPGs"
[menu] [menu]
[[menu.main]] [[menu.main]]

View File

@ -2,6 +2,7 @@
title: "About" title: "About"
date: 2023-01-04T10:09:32+01:00 date: 2023-01-04T10:09:32+01:00
draft: false draft: false
type: "page"
--- ---
I study computer science at Humboldt-Universität in Berlin, Germany. I study computer science at Humboldt-Universität in Berlin, Germany.

View File

@ -0,0 +1,81 @@
---
title: "Data Oriented Finite State Machines"
date: 2022-03-29T16:01:00+02:00
draft: false
type: "post"
---
Finite state machines (FSMs) are probably one of the most widespread techniques used for game AI.
For my own engine i thought about ways to implement FSMs with performance in mind. The fundamental assumption guiding the design was: “There will be many AI entities using the same state-machine.”[1](#fn:1)
The design itself is pretty simple and im sure that im not the first person to come up with this. The structure for a FSM is shown here:
#define XE_AI_FSM_MAX_STATES 16
#define XE_AI_FSM_STATE_FUNC(name) uint32_t name(xeAIEntity entity, uint32_t previousState)
typedef XE_AI_FSM_STATE_FUNC(xeAIFSMStateFunc);
typedef struct
{
uint32_t maxEntities;
uint32_t stateCount;
uint32_t entityCounts[XE_AI_FSM_MAX_STATES];
xeAIFSMStateEntity* stateEntities[XE_AI_FSM_MAX_STATES];
xeAIFSMStateEntity* nextStateEntities[XE_AI_FSM_MAX_STATES];
xeAIFSMStateFunc* stateFuncs[XE_AI_FSM_MAX_STATES];
} xeAIFSM;
I arbitrarily limited the number of states to 16 because this saves me from some memory management, but it would be trivial to remove this limit. For every state (which is identified by an unsigned integer between `0` and `stateCount`), the FSM saves the AI entities in that state (`stateEntities`). The type `xeAIFSMStateEntity` is shown below. It contains an reference to the entity data and the previous state of the entity, which can be used by the state function to determine if the entity just entered the state (for example, if a monster just entered the “chase player” state, we could play a special sound effect).
typedef struct
{
xeAIEntity entity;
uint32_t previousState;
} xeAIFSMStateEntity;
Every frame, all state functions need to be executed. For that, we iterate over all states and call the function for that state (`stateFuncs[stateIndex]`) for every entity in `stateEntities[stateIndex]`. This can be done in parallel, for example via a job system (which is what i do in my engine). The state function returns the new state of the entity and the run function of the FSM will place the append the entity to `nextStateEntities[newState]`. After all states were executed, `stateEntities` and `nextStateEntities` are swapped.
Code for this function is shown below; i removed all parallelization code for improved clarity.
void xe_ai_fsm_run(xeAIFSM* fsm)
{
uint32_t nextEntityCounts[XE_AI_FSM_MAX_STATES];
XE_ZERO_ARRAY(nextEntityCounts, XE_AI_FSM_MAX_STATES);
for (uint32_t state = 0; state < XE_AI_FSM_MAX_STATES; ++state) {
xeAIFSMStateFunc* stateFunc = fsm->stateFuncs[state];
assert(stateFunc);
uint32_t entityCount = fsm->entityCounts[state];
xeAIFSMStateEntity* entities = fsm->stateEntities[state];
for (uint32_t entityIdx = 0; entityIdx < entityCount; ++entityIdx) {
uint32_t nextState = stateFunc(entities[entityIdx].entity, entities[entityIdx].previousState);
entities[entityIdx].previousState = state;
if (nextState >= fsm->stateCount) {
xe_log_error("FSM %x entity (%u;%u) tried to move to invalid state %u",
(uintptr_t)fsm,
state, entityIdx,
nextState);
nextState = state;
assert(!"Tried to move an entity to an invalid state");
}
/* Move to (potentially) new state */
fsm->nextStateEntities[nextState][nextEntityCounts[nextState]] = entities[entityIdx];
++nextEntityCounts[nextState];
}
}
/* Swap pointers and update counts */
for (uint32_t state = 0; state < XE_AI_FSM_MAX_STATES; ++state) {
xeAIFSMStateEntity* tmp = fsm->stateEntities[state];
fsm->stateEntities[state] = fsm->nextStateEntities[state];
fsm->nextStateEntities[state] = tmp;
fsm->entityCounts[state] = nextEntityCounts[state];
}
}
* * *
1. “Where there is one, there are many.”, [as Mike action would say](https://youtu.be/rX0ItVEVjHc?t=871). [](#fnref:1)

View File

@ -1,6 +0,0 @@
---
title: "First_post"
date: 2023-01-04T09:39:51+01:00
draft: true
---

View File

@ -0,0 +1,60 @@
---
title: "TMUX Cheat Sheet"
date: 2022-04-08T13:20:00+02:00
draft: false
type: "post"
---
Default prefix “key” is `Ctrl + b`.
Sessions
--------
Creating a new named session:
tmux new-session -s <name>
Listing all sessions:
tmux list-session
Attach to a running session:
tmux attach -t <name>
To detach from a session, press: `<Prefix> + d`
Renaming a session:
tmux rename-session -t <old-name> <new-name>
Kill a session:
tmux kill-session -t <name>
Windows
-------
To create a new window, within a session press: `<Prefix> + c`.
To list all windows, press: `<Prefix> + w`. The list also allows us to select a window using `UP, DOWN` and `ENTER`.
To rename a window, press: `<Prefix> + ,`.
To terminate a window, press: `<Prefix> + &`.
Copy-Mode
---------
To enter copy mode, press: `<Prefix> + [`.
Begin selection with: `<Prefix> + Space`.
Hit `ENTER` to copy the selected text to tmuxs clipboard.
Paste with `<Prefix> + ]`.

View File

@ -0,0 +1,119 @@
---
title: "A Simple Vulkan Memory Manager"
date: 2022-03-10T13:54:40+01:00
draft: false
type: "post"
---
As part of my journey towards understanding the vulkan api, i tried to implement a simple memory management scheme from scratch. The core idea of the system is to divide the available memory into pools and then to sub-allocate from the appropriate pool.
A pool has the following structure:
struct xeVulkanMemoryPool
{
VkDeviceMemory deviceMemory;
VkDeviceSize poolSize;
VkDeviceSize blockSize;
xeQueue blockQueue;
};
The device memory is allocated via `vkAllocateMemory` and divided into blocks of equal size. The queue maintains the offsets of free blocks.
Allocating a block is then simply:
static xeResult xe_allocate_pool_memory(xeVulkanMemoryPool* pool, xeVulkanMemoryAllocation* allocation)
{
VkDeviceSize offset;
xeResult result = XE_SUCCESS;
if (xe_pop_queue(&pool->blockQueue, &offset) == XE_QUEUE_EMPTY)
result = XE_OUT_OF_MEMORY;
else {
allocation->deviceMemory = pool->deviceMemory;
allocation->offset = offset;
}
return result;
}
The used queue is lockless, so this function is thread-safe.
Freeing a block is done by pushing the offset into the queue.
The more interesting part comes from managing the pools. This is the responsiblity of the “memory manager”. Its structure is shown below:
struct xeVulkanMemoryManager
{
VkDevice device;
VkPhysicalDeviceMemoryProperties memoryProperties;
uint32_t maxMemoryPoolCount;
/* we track the amount of memory we allocated */
uint32_t deviceLocalBudget;
uint32_t deviceLocalHostVisibleBudget;
uint32_t usedDeviceLocalBudget;
uint32_t usedDeviceLocalHostVisibleBudget;
VkDeviceSize maxAllocationSize;
/* Hash from blockSize + memory type + required properties to the index
* of the first pool that satisfies these requirements.
* If the pool is full check memoryPoolLinks[index] for another pool.
* If no suitable pool with available memory is found, create a new pool.
*/
xeHash poolHash;
xeVulkanMemoryPool* memoryPools;
uint32_t* memoryPoolLinks;
};
The algorithm for allocating a block of device memory works as follows (pseudo-c)
xeResult xe_allocate_vulkan_memory(xeVulkanMemoryManager* memoryManager,
uint32_t memoryTypeBits,
VkMemoryPropertyFlags requiredProperties,
VkDeviceSize size,
VkDeviceSize alignment,
xeVulkanMemoryAllocation* allocation)
{
/* Determine block size from size+alignment.
* and round to nearest multiple of 64 kb.
* This keeps the number of different pools smaller. */
size = XE_MAX(size, alignment);
size = round_to_multiple(size, XE_KB(64));
/* Construct the lookup key.
* The lower 32 bits are made up of the memory type and the block size,
* the upper 32 bits contain the required memory properties */
uint64_t log2BlockSize = log2(size);
uint32_t memoryTypeIndex = find_vulkan_memory_type_index(memoryManager, memoryTypeBits, requiredProperties);
uint64_t key = memoryTypeIndex | (log2BlockSize << VK_MAX_MEMORY_TYPES) | (uint64_t)requiredProperties << 32);
uint32_t poolIndex = xe_hash_lookup(memoryManager->poolHash, key, NO_SUITABLE_POOL);
if (poolIndex == NO_SUITABLE_POOL) {
/* Create a new pool with the required parameters, insert into the hash table and allocate from that pool */
}
else {
/* Try to allocate from that pool */
if (xe_allocate_pool_memory(&memoryManager->memoryPools[poolIndex], allocation) == XE_SUCCESS)
return XE_SUCCESS;
/* Try the next suitable pool */
uint32_t lastValidPoolIndex = poolIndex;
while (1) {
if (poolIndex == NO_SUITABLE_POOL)
break;
poolIndex = memoryManager->memoryPoolLinks[poolIndex];
if (xe_allocate_pool_memory(&memoryManager->memoryPools[poolIndex], allocation) == XE_SUCCESS)
return XE_SUCCESS;
lastValidPoolIndex = poolIndex;
}
/* Create a new pool, update memoryPoolLinks[lastValidPoolIndex] and allocate from the new pool */
}
/* Allocation failed */
return XE_OUT_OF_MEMORY;
}
This system is probably not as sophisticated as the one used by [VMA](https://gpuopen.com/vulkan-memory-allocator/) but it is easy to understand, which in my opinion is the most valuable property of a learning project.

View File

@ -2,6 +2,7 @@
title: "Projects" title: "Projects"
date: 2023-01-04T10:18:30+01:00 date: 2023-01-04T10:18:30+01:00
draft: false draft: false
type: "page"
--- ---
You can find the source code for some of these projects at [gitea](https://libneat.hopto.org/git). You can find the source code for some of these projects at [gitea](https://libneat.hopto.org/git).
@ -27,3 +28,9 @@ The project is currently on hold, but the idea might be re-used at some point.
My bachelor thesis delt with implementing an GPU algorithm for time series comparison. My bachelor thesis delt with implementing an GPU algorithm for time series comparison.
A paper describing the implementation was published at the [PARS-Workshop 2019](https://fg-pars.gi.de/veranstaltung/pars-workshop-2019). A paper describing the implementation was published at the [PARS-Workshop 2019](https://fg-pars.gi.de/veranstaltung/pars-workshop-2019).
The paper itself can be found [here at page 63](https://dl.gi.de/bitstream/handle/20.500.12116/33861/PARS-Mitteilungen_2019.pdf).
# Krimi Dinner Engine
A simple base layer for a murder mystery dinner game.
The game itself is not by me, but i contributed the basic technology for rendering and android & windows compatibility.