This commit is contained in:
User
2026-01-29 17:06:34 +01:00
commit 17c1ae1638
77 changed files with 25724 additions and 0 deletions

134
.drone.yml Normal file
View File

@@ -0,0 +1,134 @@
---
kind: pipeline
type: docker
name: default
steps:
- name: build_image_base
pull: never
image: bash_n_docker
volumes:
- name: dockersock
path: /var/run/docker.sock
- name: docker_config
path: /docker_config/config.json
commands:
- export DOCKER_CONFIG=/docker_config
- docker build -f docker/Dockerfile_Base -t spacegame_base .
- name: build_image_linux
pull: never
image: bash_n_docker
depends_on:
- build_image_base
volumes:
- name: dockersock
path: /var/run/docker.sock
- name: docker_config
path: /docker_config/config.json
commands:
- export DOCKER_CONFIG=/docker_config
- docker build -f docker/Dockerfile_Linux -t spacegame_linux .
- name: build_image_windows
pull: never
image: bash_n_docker
depends_on:
- build_image_base
volumes:
- name: dockersock
path: /var/run/docker.sock
- name: docker_config
path: /docker_config/config.json
commands:
- export DOCKER_CONFIG=/docker_config
- docker build -f docker/Dockerfile_Windows -t spacegame_windows .
- name: build_game_linux
pull: never
image: spacegame_linux
depends_on:
- build_image_linux
commands:
- >
cmake -S . -B build_linux -G Ninja
-DCMAKE_TOOLCHAIN_FILE="cmake/clang_toolchain.cmake"
-DCMAKE_BUILD_TYPE=Release
-DSPACEGAME_BUILD_SHADERS=ON
- cmake --build build_linux --verbose -- -l $(($(nproc)+4))
- name: build_game_windows
pull: never
image: spacegame_windows
depends_on:
- build_image_windows
commands:
- >
cmake -S . -B build_windows -G Ninja
-DCMAKE_TOOLCHAIN_FILE="cmake/clang_mingw_toolchain.cmake"
-DCMAKE_BUILD_TYPE=Release
-DSPACEGAME_BUILD_SHADERS=OFF
- cmake --build build_windows --verbose -- -l $(($(nproc)+4))
- name: package_game
pull: never
image: spacegame_linux
depends_on:
- build_game_linux
- build_game_windows
when:
event:
- tag
commands:
# We need to fetch the release tag in order to get its message
- git fetch origin tag $DRONE_TAG --no-tags
# Get short version from tag name and long version from tag message
# - VERSION_SHORT=$DRONE_TAG
- VERSION_LONG=$(git for-each-ref refs/tags/$DRONE_TAG --format='%(contents)')
# Package linux
- LINUX_VERSION="$VERSION_LONG.linux"
- mkdir spacegame_linux
- echo $LINUX_VERSION > spacegame_linux/version.txt
- cp build_linux/bin/x86_64/Release/spacegame spacegame_linux/spacegame
- cp -r assets spacegame_linux/assets
- zip -r spacegame_linux.zip spacegame_linux
# Package windows
- WINDOWS_VERSION="$VERSION_LONG.windows"
- mkdir spacegame_windows
- echo $WINDOWS_VERSION > spacegame_windows/version.txt
- cp build_windows/bin/x86_64/Release/spacegame.exe spacegame_windows/spacegame.exe
- cp -r assets spacegame_windows/assets
- zip -r spacegame_windows.zip spacegame_windows
- name: release
image: plugins/gitea-release
depends_on:
- package_game
when:
event:
- tag
settings:
api_key:
from_secret: api_key
base_url: https://git.lph.zone
prerelease: true
files:
- spacegame_linux.zip
- spacegame_windows.zip
volumes:
- name: dockersock
host:
path: /var/run/docker.sock
- name: docker_config
host:
path: /home/crydsch/.docker/config.json
image_pull_secrets:
- dockerconfig

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
.vscode
build*
assets/shaders/*.spv
atlas

5
3rdparty/.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
glfw
bx
bimg
bgfx
bgfx.cmake

20
3rdparty/README.md vendored Normal file
View File

@@ -0,0 +1,20 @@
## emilib: Loose collection of misc C++ libs
Repo.: https://github.com/emilk/emilib
## stb: single-file public domain libraries for C/C++
Repo.: https://github.com/nothings/stb
## bgfx for rendering
Repos.:
- bx
- bimg
- bgfx
- bgfx.cmake
## glfw for window creation

671
3rdparty/emilib/hash_map.hpp vendored Normal file
View File

@@ -0,0 +1,671 @@
// By Emil Ernerfeldt 2014-2017
// LICENSE:
// This software is dual-licensed to the public domain and under the following
// license: you are granted a perpetual, irrevocable license to copy, modify,
// publish, and distribute this file as you see fit.
#pragma once
#include <cstdlib>
#include <iterator>
#include <utility>
#include <assert.h>
namespace emilib {
// (Crydsch) fixes for standalone usage
#define DCHECK_NE_F(x,y) assert((x) != (y))
#define DCHECK_F(x) assert(x)
#define DCHECK_EQ_F(x,y) assert((x) == (y))
/// like std::equal_to but no need to #include <functional>
template<typename T>
struct HashMapEqualTo
{
constexpr bool operator()(const T& lhs, const T& rhs) const
{
return lhs == rhs;
}
};
/// A cache-friendly hash table with open addressing, linear probing and power-of-two capacity
template <typename KeyT, typename ValueT, typename HashT = std::hash<KeyT>, typename EqT = HashMapEqualTo<KeyT>>
class HashMap
{
private:
using MyType = HashMap<KeyT, ValueT, HashT, EqT>;
using PairT = std::pair<KeyT, ValueT>;
public:
using size_type = size_t;
using value_type = PairT;
using reference = PairT&;
using const_reference = const PairT&;
class iterator
{
public:
using iterator_category = std::forward_iterator_tag;
using difference_type = size_t;
using distance_type = size_t;
using value_type = std::pair<KeyT, ValueT>;
using pointer = value_type*;
using reference = value_type&;
iterator() { }
iterator(MyType* hash_map, size_t bucket) : _map(hash_map), _bucket(bucket)
{
}
iterator& operator++()
{
this->goto_next_element();
return *this;
}
iterator operator++(int)
{
size_t old_index = _bucket;
this->goto_next_element();
return iterator(_map, old_index);
}
reference operator*() const
{
return _map->_pairs[_bucket];
}
pointer operator->() const
{
return _map->_pairs + _bucket;
}
bool operator==(const iterator& rhs) const
{
DCHECK_EQ_F(_map, rhs._map);
return this->_bucket == rhs._bucket;
}
bool operator!=(const iterator& rhs) const
{
DCHECK_EQ_F(_map, rhs._map);
return this->_bucket != rhs._bucket;
}
private:
void goto_next_element()
{
DCHECK_LT_F(_bucket, _map->_num_buckets);
do {
_bucket++;
} while (_bucket < _map->_num_buckets && _map->_states[_bucket] != State::FILLED);
}
//private:
// friend class MyType;
public:
MyType* _map;
size_t _bucket;
};
class const_iterator
{
public:
using iterator_category = std::forward_iterator_tag;
using difference_type = size_t;
using distance_type = size_t;
using value_type = const std::pair<KeyT, ValueT>;
using pointer = value_type*;
using reference = value_type&;
const_iterator() { }
const_iterator(iterator proto) : _map(proto._map), _bucket(proto._bucket)
{
}
const_iterator(const MyType* hash_map, size_t bucket) : _map(hash_map), _bucket(bucket)
{
}
const_iterator& operator++()
{
this->goto_next_element();
return *this;
}
const_iterator operator++(int)
{
size_t old_index = _bucket;
this->goto_next_element();
return const_iterator(_map, old_index);
}
reference operator*() const
{
return _map->_pairs[_bucket];
}
pointer operator->() const
{
return _map->_pairs + _bucket;
}
bool operator==(const const_iterator& rhs) const
{
DCHECK_EQ_F(_map, rhs._map);
return this->_bucket == rhs._bucket;
}
bool operator!=(const const_iterator& rhs) const
{
DCHECK_EQ_F(_map, rhs._map);
return this->_bucket != rhs._bucket;
}
private:
void goto_next_element()
{
DCHECK_LT_F(_bucket, _map->_num_buckets);
do {
_bucket++;
} while (_bucket < _map->_num_buckets && _map->_states[_bucket] != State::FILLED);
}
//private:
// friend class MyType;
public:
const MyType* _map;
size_t _bucket;
};
// ------------------------------------------------------------------------
HashMap() = default;
HashMap(const HashMap& other)
{
reserve(other.size());
insert(other.cbegin(), other.cend());
}
HashMap(HashMap&& other)
{
*this = std::move(other);
}
HashMap& operator=(const HashMap& other)
{
clear();
reserve(other.size());
insert(other.cbegin(), other.cend());
return *this;
}
void operator=(HashMap&& other)
{
this->swap(other);
}
~HashMap()
{
for (size_t bucket=0; bucket<_num_buckets; ++bucket) {
if (_states[bucket] == State::FILLED) {
_pairs[bucket].~PairT();
}
}
free(_states);
free(_pairs);
}
void swap(HashMap& other)
{
std::swap(_hasher, other._hasher);
std::swap(_eq, other._eq);
std::swap(_states, other._states);
std::swap(_pairs, other._pairs);
std::swap(_num_buckets, other._num_buckets);
std::swap(_num_filled, other._num_filled);
std::swap(_max_probe_length, other._max_probe_length);
std::swap(_mask, other._mask);
}
// -------------------------------------------------------------
iterator begin()
{
size_t bucket = 0;
while (bucket<_num_buckets && _states[bucket] != State::FILLED) {
++bucket;
}
return iterator(this, bucket);
}
const_iterator cbegin() const
{
size_t bucket = 0;
while (bucket<_num_buckets && _states[bucket] != State::FILLED) {
++bucket;
}
return const_iterator(this, bucket);
}
const_iterator begin() const
{
return cbegin();
}
iterator end()
{
return iterator(this, _num_buckets);
}
const_iterator cend() const
{
return const_iterator(this, _num_buckets);
}
const_iterator end() const
{
return cend();
}
size_t size() const
{
return _num_filled;
}
bool empty() const
{
return _num_filled==0;
}
// Returns the number of buckets.
size_t bucket_count() const
{
return _num_buckets;
}
/// Returns average number of elements per bucket.
float load_factor() const
{
return static_cast<float>(_num_filled) / static_cast<float>(_num_buckets);
}
// ------------------------------------------------------------
template<typename KeyLike>
iterator find(const KeyLike& key)
{
auto bucket = this->find_filled_bucket(key);
if (bucket == (size_t)-1) {
return this->end();
}
return iterator(this, bucket);
}
template<typename KeyLike>
const_iterator find(const KeyLike& key) const
{
auto bucket = this->find_filled_bucket(key);
if (bucket == (size_t)-1)
{
return this->end();
}
return const_iterator(this, bucket);
}
template<typename KeyLike>
bool contains(const KeyLike& k) const
{
return find_filled_bucket(k) != (size_t)-1;
}
template<typename KeyLike>
size_t count(const KeyLike& k) const
{
return find_filled_bucket(k) != (size_t)-1 ? 1 : 0;
}
/// Returns the matching ValueT or nullptr if k isn't found.
template<typename KeyLike>
ValueT* try_get(const KeyLike& k)
{
auto bucket = find_filled_bucket(k);
if (bucket != (size_t)-1) {
return &_pairs[bucket].second;
} else {
return nullptr;
}
}
/// Const version of the above
template<typename KeyLike>
const ValueT* try_get(const KeyLike& k) const
{
auto bucket = find_filled_bucket(k);
if (bucket != (size_t)-1) {
return &_pairs[bucket].second;
} else {
return nullptr;
}
}
/// Convenience function.
template<typename KeyLike>
const ValueT get_or_return_default(const KeyLike& k) const
{
const ValueT* ret = try_get(k);
if (ret) {
return *ret;
} else {
return ValueT();
}
}
// -----------------------------------------------------
/// Returns a pair consisting of an iterator to the inserted element
/// (or to the element that prevented the insertion)
/// and a bool denoting whether the insertion took place.
std::pair<iterator, bool> insert(const KeyT& key, const ValueT& value)
{
check_expand_need();
auto bucket = find_or_allocate(key);
if (_states[bucket] == State::FILLED) {
return { iterator(this, bucket), false };
} else {
_states[bucket] = State::FILLED;
new(_pairs + bucket) PairT(key, value);
_num_filled++;
return { iterator(this, bucket), true };
}
}
std::pair<iterator, bool> insert(const std::pair<KeyT, ValueT>& p)
{
return insert(p.first, p.second);
}
void insert(const_iterator begin, const_iterator end)
{
// TODO: reserve space exactly once.
for (; begin != end; ++begin) {
insert(begin->first, begin->second);
}
}
/// Same as above, but contains(key) MUST be false
void insert_unique(KeyT&& key, ValueT&& value)
{
DCHECK_F(!contains(key));
check_expand_need();
auto bucket = find_empty_bucket(key);
_states[bucket] = State::FILLED;
new(_pairs + bucket) PairT(std::move(key), std::move(value));
_num_filled++;
}
void insert_unique(std::pair<KeyT, ValueT>&& p)
{
insert_unique(std::move(p.first), std::move(p.second));
}
void insert_or_assign(const KeyT& key, ValueT&& value)
{
check_expand_need();
auto bucket = find_or_allocate(key);
// Check if inserting a new value rather than overwriting an old entry
if (_states[bucket] == State::FILLED) {
_pairs[bucket].second = value;
} else {
_states[bucket] = State::FILLED;
new(_pairs + bucket) PairT(key, value);
_num_filled++;
}
}
/// Return the old value or ValueT() if it didn't exist.
ValueT set_get(const KeyT& key, const ValueT& new_value)
{
check_expand_need();
auto bucket = find_or_allocate(key);
// Check if inserting a new value rather than overwriting an old entry
if (_states[bucket] == State::FILLED) {
ValueT old_value = _pairs[bucket].second;
_pairs[bucket] = new_value.second;
return old_value;
} else {
_states[bucket] = State::FILLED;
new(_pairs + bucket) PairT(key, new_value);
_num_filled++;
return ValueT();
}
}
/// Like std::map<KeyT,ValueT>::operator[].
ValueT& operator[](const KeyT& key)
{
check_expand_need();
auto bucket = find_or_allocate(key);
/* Check if inserting a new value rather than overwriting an old entry */
if (_states[bucket] != State::FILLED) {
_states[bucket] = State::FILLED;
new(_pairs + bucket) PairT(key, ValueT());
_num_filled++;
}
return _pairs[bucket].second;
}
// -------------------------------------------------------
/// Erase an element from the hash table.
/// return false if element was not found
bool erase(const KeyT& key)
{
auto bucket = find_filled_bucket(key);
if (bucket != (size_t)-1) {
_states[bucket] = State::ACTIVE;
_pairs[bucket].~PairT();
_num_filled -= 1;
return true;
} else {
return false;
}
}
/// Erase an element using an iterator.
/// Returns an iterator to the next element (or end()).
iterator erase(iterator it)
{
DCHECK_EQ_F(it._map, this);
DCHECK_LT_F(it._bucket, _num_buckets);
_states[it._bucket] = State::ACTIVE;
_pairs[it._bucket].~PairT();
_num_filled -= 1;
return ++it;
}
/// Remove all elements, keeping full capacity.
void clear()
{
for (size_t bucket=0; bucket<_num_buckets; ++bucket) {
if (_states[bucket] == State::FILLED) {
_states[bucket] = State::INACTIVE;
_pairs[bucket].~PairT();
}
}
_num_filled = 0;
_max_probe_length = -1;
}
/// Make room for this many elements
void reserve(size_t num_elems)
{
size_t required_buckets = num_elems + num_elems/2 + 1;
if (required_buckets <= _num_buckets) {
return;
}
size_t num_buckets = 4;
while (num_buckets < required_buckets) { num_buckets *= 2; }
auto new_states = (State*)malloc(num_buckets * sizeof(State));
auto new_pairs = (PairT*)malloc(num_buckets * sizeof(PairT));
if (!new_states || !new_pairs) {
free(new_states);
free(new_pairs);
// throw std::bad_alloc();
std::abort();
}
//auto old_num_filled = _num_filled;
auto old_num_buckets = _num_buckets;
auto old_states = _states;
auto old_pairs = _pairs;
_num_filled = 0;
_num_buckets = num_buckets;
_mask = _num_buckets - 1;
_states = new_states;
_pairs = new_pairs;
std::fill_n(_states, num_buckets, State::INACTIVE);
_max_probe_length = -1;
for (size_t src_bucket=0; src_bucket<old_num_buckets; src_bucket++) {
if (old_states[src_bucket] == State::FILLED) {
auto& src_pair = old_pairs[src_bucket];
auto dst_bucket = find_empty_bucket(src_pair.first);
DCHECK_NE_F(dst_bucket, (size_t)-1);
DCHECK_NE_F(_states[dst_bucket], State::FILLED);
_states[dst_bucket] = State::FILLED;
new(_pairs + dst_bucket) PairT(std::move(src_pair));
_num_filled += 1;
src_pair.~PairT();
}
}
//DCHECK_EQ_F(old_num_filled, _num_filled);
free(old_states);
free(old_pairs);
}
private:
// Can we fit another element?
void check_expand_need()
{
reserve(_num_filled + 1);
}
// Find the bucket with this key, or return (size_t)-1
template<typename KeyLike>
size_t find_filled_bucket(const KeyLike& key) const
{
if (empty()) { return (size_t)-1; } // Optimization
auto hash_value = _hasher(key);
for (int offset=0; offset<=_max_probe_length; ++offset) {
auto bucket = (hash_value + offset) & _mask;
if (_states[bucket] == State::FILLED) {
if (_eq(_pairs[bucket].first, key)) {
return bucket;
}
} else if (_states[bucket] == State::INACTIVE) {
return (size_t)-1; // End of the chain!
}
}
return (size_t)-1;
}
// Find the bucket with this key, or return a good empty bucket to place the key in.
// In the latter case, the bucket is expected to be filled.
size_t find_or_allocate(const KeyT& key)
{
auto hash_value = _hasher(key);
size_t hole = (size_t)-1;
int offset=0;
for (; offset<=_max_probe_length; ++offset) {
auto bucket = (hash_value + offset) & _mask;
if (_states[bucket] == State::FILLED) {
if (_eq(_pairs[bucket].first, key)) {
return bucket;
}
} else if (_states[bucket] == State::INACTIVE) {
return bucket;
} else {
// ACTIVE: keep searching
if (hole == (size_t)-1) {
hole = bucket;
}
}
}
// No key found - but maybe a hole for it
assert(offset == _max_probe_length+1);
if (hole != (size_t)-1) {
return hole;
}
// No hole found within _max_probe_length
for (; ; ++offset) {
auto bucket = (hash_value + offset) & _mask;
if (_states[bucket] != State::FILLED) {
_max_probe_length = offset;
return bucket;
}
}
}
// key is not in this map. Find a place to put it.
size_t find_empty_bucket(const KeyT& key)
{
auto hash_value = _hasher(key);
for (int offset=0; ; ++offset) {
auto bucket = (hash_value + offset) & _mask;
if (_states[bucket] != State::FILLED) {
if (offset > _max_probe_length) {
_max_probe_length = offset;
}
return bucket;
}
}
}
private:
enum class State : uint8_t
{
INACTIVE, // Never been touched
ACTIVE, // Is inside a search-chain, but is empty
FILLED // Is set with key/value
};
HashT _hasher;
EqT _eq;
State* _states = nullptr;
PairT* _pairs = nullptr;
size_t _num_buckets = 0;
size_t _num_filled = 0;
int _max_probe_length = -1; // Our longest bucket-brigade is this long. ONLY when we have zero elements is this ever negative (-1).
size_t _mask = 0; // _num_buckets minus one
};
} // namespace emilib

586
3rdparty/emilib/hash_set.hpp vendored Normal file
View File

@@ -0,0 +1,586 @@
// By Emil Ernerfeldt 2014-2016
// LICENSE:
// This software is dual-licensed to the public domain and under the following
// license: you are granted a perpetual, irrevocable license to copy, modify,
// publish, and distribute this file as you see fit.
#pragma once
#include <cstdlib> // malloc
#include <iterator>
#include <utility>
#include <assert.h>
namespace emilib {
/// like std::equal_to but no need to `#include <functional>`
template<typename T>
struct HashSetEqualTo
{
constexpr bool operator()(const T& lhs, const T& rhs) const
{
return lhs == rhs;
}
};
/// A cache-friendly hash set with open addressing, linear probing and power-of-two capacity
template <typename KeyT, typename HashT = std::hash<KeyT>, typename EqT = HashSetEqualTo<KeyT>>
class HashSet
{
private:
using MyType = HashSet<KeyT, HashT, EqT>;
public:
using size_type = size_t;
using value_type = KeyT;
using reference = KeyT&;
using const_reference = const KeyT&;
class iterator
{
public:
using iterator_category = std::forward_iterator_tag;
using difference_type = size_t;
using distance_type = size_t;
using value_type = KeyT;
using pointer = value_type*;
using reference = value_type&;
iterator() { }
iterator(MyType* hash_set, size_t bucket) : _set(hash_set), _bucket(bucket)
{
}
iterator& operator++()
{
this->goto_next_element();
return *this;
}
iterator operator++(int)
{
size_t old_index = _bucket;
this->goto_next_element();
return iterator(_set, old_index);
}
reference operator*() const
{
return _set->_keys[_bucket];
}
pointer operator->() const
{
return _set->_keys + _bucket;
}
bool operator==(const iterator& rhs) const
{
DCHECK_EQ_F(_set, rhs._set);
return this->_bucket == rhs._bucket;
}
bool operator!=(const iterator& rhs) const
{
DCHECK_EQ_F(_set, rhs._set);
return this->_bucket != rhs._bucket;
}
private:
void goto_next_element()
{
DCHECK_LT_F(_bucket, _set->_num_buckets);
do {
_bucket++;
} while (_bucket < _set->_num_buckets && _set->_states[_bucket] != State::FILLED);
}
//private:
// friend class MyType;
public:
MyType* _set;
size_t _bucket;
};
class const_iterator
{
public:
using iterator_category = std::forward_iterator_tag;
using difference_type = size_t;
using distance_type = size_t;
using value_type = const KeyT;
using pointer = value_type*;
using reference = value_type&;
const_iterator() { }
const_iterator(iterator proto) : _set(proto._set), _bucket(proto._bucket)
{
}
const_iterator(const MyType* hash_set, size_t bucket) : _set(hash_set), _bucket(bucket)
{
}
const_iterator& operator++()
{
this->goto_next_element();
return *this;
}
const_iterator operator++(int)
{
size_t old_index = _bucket;
this->goto_next_element();
return const_iterator(_set, old_index);
}
reference operator*() const
{
return _set->_keys[_bucket];
}
pointer operator->() const
{
return _set->_keys + _bucket;
}
bool operator==(const const_iterator& rhs) const
{
DCHECK_EQ_F(_set, rhs._set);
return this->_bucket == rhs._bucket;
}
bool operator!=(const const_iterator& rhs) const
{
DCHECK_EQ_F(_set, rhs._set);
return this->_bucket != rhs._bucket;
}
private:
void goto_next_element()
{
DCHECK_LT_F(_bucket, _set->_num_buckets);
do {
_bucket++;
} while (_bucket < _set->_num_buckets && _set->_states[_bucket] != State::FILLED);
}
//private:
// friend class MyType;
public:
const MyType* _set;
size_t _bucket;
};
// ------------------------------------------------------------------------
HashSet() = default;
HashSet(const HashSet& other)
{
reserve(other.size());
insert(other.cbegin(), other.cend());
}
HashSet(HashSet&& other)
{
*this = std::move(other);
}
HashSet& operator=(const HashSet& other)
{
clear();
reserve(other.size());
insert(other.cbegin(), other.cend());
return *this;
}
void operator=(HashSet&& other)
{
this->swap(other);
}
~HashSet()
{
for (size_t bucket=0; bucket<_num_buckets; ++bucket) {
if (_states[bucket] == State::FILLED) {
_keys[bucket].~KeyT();
}
}
free(_states);
free(_keys);
}
void swap(HashSet& other)
{
std::swap(_hasher, other._hasher);
std::swap(_eq, other._eq);
std::swap(_states, other._states);
std::swap(_keys, other._keys);
std::swap(_num_buckets, other._num_buckets);
std::swap(_num_filled, other._num_filled);
std::swap(_max_probe_length, other._max_probe_length);
std::swap(_mask, other._mask);
}
// -------------------------------------------------------------
iterator begin()
{
size_t bucket = 0;
while (bucket<_num_buckets && _states[bucket] != State::FILLED) {
++bucket;
}
return iterator(this, bucket);
}
const_iterator cbegin() const
{
size_t bucket = 0;
while (bucket<_num_buckets && _states[bucket] != State::FILLED) {
++bucket;
}
return const_iterator(this, bucket);
}
const_iterator begin() const
{
return cbegin();
}
iterator end()
{
return iterator(this, _num_buckets);
}
const_iterator cend() const
{
return const_iterator(this, _num_buckets);
}
const_iterator end() const
{
return cend();
}
size_t size() const
{
return _num_filled;
}
bool empty() const
{
return _num_filled==0;
}
// Returns the number of buckets.
size_t bucket_count() const
{
return _num_buckets;
}
/// Returns average number of elements per bucket.
float load_factor() const
{
return static_cast<float>(_num_filled) / static_cast<float>(_num_buckets);
}
// ------------------------------------------------------------
iterator find(const KeyT& key)
{
auto bucket = this->find_filled_bucket(key);
if (bucket == (size_t)-1) {
return this->end();
}
return iterator(this, bucket);
}
const_iterator find(const KeyT& key) const
{
auto bucket = this->find_filled_bucket(key);
if (bucket == (size_t)-1) {
return this->end();
}
return const_iterator(this, bucket);
}
bool contains(const KeyT& k) const
{
return find_filled_bucket(k) != (size_t)-1;
}
size_t count(const KeyT& k) const
{
return find_filled_bucket(k) != (size_t)-1 ? 1 : 0;
}
// -----------------------------------------------------
/// Insert an element, unless it already exists.
/// Returns a pair consisting of an iterator to the inserted element
/// (or to the element that prevented the insertion)
/// and a bool denoting whether the insertion took place.
std::pair<iterator, bool> insert(const KeyT& key)
{
check_expand_need();
auto bucket = find_or_allocate(key);
if (_states[bucket] == State::FILLED) {
return { iterator(this, bucket), false };
} else {
_states[bucket] = State::FILLED;
new(_keys + bucket) KeyT(key);
_num_filled++;
return { iterator(this, bucket), true };
}
}
/// Insert an element, unless it already exists.
/// Returns a pair consisting of an iterator to the inserted element
/// (or to the element that prevented the insertion)
/// and a bool denoting whether the insertion took place.
std::pair<iterator, bool> insert(KeyT&& key)
{
check_expand_need();
auto bucket = find_or_allocate(key);
if (_states[bucket] == State::FILLED) {
return { iterator(this, bucket), false };
} else {
_states[bucket] = State::FILLED;
new(_keys + bucket) KeyT(std::move(key));
_num_filled++;
return { iterator(this, bucket), true };
}
}
template<class... Args>
std::pair<iterator, bool> emplace(Args&&... args)
{
return insert(KeyT(std::forward<Args>(args)...));
}
void insert(const_iterator begin, const_iterator end)
{
// TODO: reserve space exactly once.
for (; begin != end; ++begin) {
insert(*begin);
}
}
/// Same as above, but contains(key) MUST be false
void insert_unique(KeyT key)
{
DCHECK_F(!contains(key));
check_expand_need();
auto bucket = find_empty_bucket(key);
_states[bucket] = State::FILLED;
new(_keys + bucket) KeyT(std::move(key));
_num_filled++;
}
// -------------------------------------------------------
/// Erase an element from the hash set.
/// return false if element was not found.
bool erase(const KeyT& key)
{
auto bucket = find_filled_bucket(key);
if (bucket != (size_t)-1) {
_states[bucket] = State::ACTIVE;
_keys[bucket].~KeyT();
_num_filled -= 1;
return true;
} else {
return false;
}
}
/// Erase an element using an iterator.
/// Returns an iterator to the next element (or end()).
iterator erase(iterator it)
{
DCHECK_EQ_F(it._set, this);
DCHECK_LT_F(it._bucket, _num_buckets);
_states[it._bucket] = State::ACTIVE;
_keys[it._bucket].~KeyT();
_num_filled -= 1;
return ++it;
}
/// Remove all elements, keeping full capacity.
void clear()
{
for (size_t bucket=0; bucket<_num_buckets; ++bucket) {
if (_states[bucket] == State::FILLED) {
_states[bucket] = State::INACTIVE;
_keys[bucket].~KeyT();
}
}
_num_filled = 0;
_max_probe_length = -1;
}
/// Make room for this many elements
void reserve(size_t num_elems)
{
size_t required_buckets = num_elems + num_elems/2 + 1;
if (required_buckets <= _num_buckets) {
return;
}
size_t num_buckets = 4;
while (num_buckets < required_buckets) { num_buckets *= 2; }
auto new_states = (State*)malloc(num_buckets * sizeof(State));
auto new_keys = (KeyT*)malloc(num_buckets * sizeof(KeyT));
if (!new_states || !new_keys) {
free(new_states);
free(new_keys);
// throw std::bad_alloc();
std::abort();
}
// auto old_num_filled = _num_filled;
auto old_num_buckets = _num_buckets;
auto old_states = _states;
auto old_keys = _keys;
_num_filled = 0;
_num_buckets = num_buckets;
_mask = _num_buckets - 1;
_states = new_states;
_keys = new_keys;
std::fill_n(_states, num_buckets, State::INACTIVE);
_max_probe_length = -1;
for (size_t src_bucket=0; src_bucket<old_num_buckets; src_bucket++) {
if (old_states[src_bucket] == State::FILLED) {
auto& src = old_keys[src_bucket];
auto dst_bucket = find_empty_bucket(src);
DCHECK_NE_F(dst_bucket, (size_t)-1);
DCHECK_NE_F(_states[dst_bucket], State::FILLED);
_states[dst_bucket] = State::FILLED;
new(_keys + dst_bucket) KeyT(std::move(src));
_num_filled += 1;
src.~KeyT();
}
}
// DCHECK_EQ_F(old_num_filled, _num_filled);
free(old_states);
free(old_keys);
}
private:
// Can we fit another element?
void check_expand_need()
{
reserve(_num_filled + 1);
}
// Find the bucket with this key, or return (size_t)-1
size_t find_filled_bucket(const KeyT& key) const
{
if (empty()) { return (size_t)-1; } // Optimization
auto hash_value = _hasher(key);
for (int offset=0; offset<=_max_probe_length; ++offset) {
auto bucket = (hash_value + offset) & _mask;
if (_states[bucket] == State::FILLED) {
if (_eq(_keys[bucket], key)) {
return bucket;
}
} else if (_states[bucket] == State::INACTIVE) {
return (size_t)-1; // End of the chain!
}
}
return (size_t)-1;
}
// Find the bucket with this key, or return a good empty bucket to place the key in.
// In the latter case, the bucket is expected to be filled.
size_t find_or_allocate(const KeyT& key)
{
auto hash_value = _hasher(key);
size_t hole = (size_t)-1;
int offset=0;
for (; offset<=_max_probe_length; ++offset) {
auto bucket = (hash_value + offset) & _mask;
if (_states[bucket] == State::FILLED) {
if (_eq(_keys[bucket], key)) {
return bucket;
}
} else if (_states[bucket] == State::INACTIVE) {
return bucket;
} else {
// ACTIVE: keep searching
if (hole == (size_t)-1) {
hole = bucket;
}
}
}
// No key found - but maybe a hole for it
assert(offset == _max_probe_length+1);
if (hole != (size_t)-1) {
return hole;
}
// No hole found within _max_probe_length
for (; ; ++offset) {
auto bucket = (hash_value + offset) & _mask;
if (_states[bucket] != State::FILLED) {
_max_probe_length = offset;
return bucket;
}
}
}
// key is not in this map. Find a place to put it.
size_t find_empty_bucket(const KeyT& key)
{
auto hash_value = _hasher(key);
for (int offset=0; ; ++offset) {
auto bucket = (hash_value + offset) & _mask;
if (_states[bucket] != State::FILLED) {
if (offset > _max_probe_length) {
_max_probe_length = offset;
}
return bucket;
}
}
}
private:
enum class State : uint8_t
{
INACTIVE, // Never been touched
ACTIVE, // Is inside a search-chain, but is empty
FILLED // Is set with key/value
};
HashT _hasher;
EqT _eq;
State* _states = nullptr;
KeyT* _keys = nullptr;
size_t _num_buckets = 0;
size_t _num_filled = 0;
int _max_probe_length = -1; // Our longest bucket-brigade is this long. ONLY when we have zero elements is this ever negative (-1).
size_t _mask = 0; // _num_buckets minus one
};
} // namespace emilib

7985
3rdparty/stb/stb_image.h vendored Normal file

File diff suppressed because it is too large Load Diff

1724
3rdparty/stb/stb_image_write.h vendored Normal file

File diff suppressed because it is too large Load Diff

85
CMakeLists.txt Normal file
View File

@@ -0,0 +1,85 @@
cmake_minimum_required(VERSION 3.21)
cmake_policy(SET CMP0091 NEW)
project("spacegame"
VERSION 0.2.2
DESCRIPTION "Blocky spacegame by crydsch@lph.zone"
LANGUAGES CXX
)
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0 -ggdb")
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -O0 -ggdb")
# Enable compiler warnings
set(SPACEGAME_COMPILER_WARNINGS
-Wall -Wextra -pedantic
-Wcast-align -Wcast-qual -Wformat=2 -Winit-self -Wmissing-declarations
-Wmissing-include-dirs -Wredundant-decls -Wswitch-default
-Wundef -Wctor-dtor-privacy
)
# Enable compile_commands.json generation for other tools
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
if(NOT EXISTS /spacegame_deps)
message(FATAL_ERROR "'/spacegame_deps' not found!\nYou need to build the required dependecies first.\nSee 'scripts/build_deps.sh'")
else()
# Search for our pre-built dependencies first
set(CMAKE_PREFIX_PATH /spacegame_deps)
endif()
list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake")
# Note: shaderc must be run on the host system
option(SPACEGAME_BUILD_SHADERS "Use shaderc to build spirv shaders" OFF)
################ dependencies ################
set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED)
find_package(glfw3 REQUIRED)
# This finds import libraries: bgfx::bgfx, bgfx::bx and bgfx::bimg
# Linking with these will bring: include dirs, compiler options, compiler defs, link libraries
find_package(bgfx REQUIRED)
# bgfx::bgfx wrongly links with: OpenGL,GLX and others
# So we link to libbgfx.a only
find_library(BGFX NAMES libbgfx.a REQUIRED)
if(SPACEGAME_BUILD_SHADERS)
find_program(SHADERC shaderc REQUIRED)
endif()
################## sources ###################
if(SPACEGAME_BUILD_SHADERS)
add_subdirectory(shaders)
endif()
# Game executable is here:
add_subdirectory(src)
################# debugging ##################
# A useful trick is to append "-LAH" to the cmake command
# of libraries to see their available options.
#
# Uncomment this section to have cmake print all variables.
# This is useful to find the actual variable names of the libraries in use.
# Such as, is it ${SMTH_INCLUDE_DIR} or ${SMTH_INCLUDE_DIRS}?
# get_cmake_property(_variableNames VARIABLES)
# list (SORT _variableNames)
# foreach (_variableName ${_variableNames})
# message(STATUS "${_variableName}=${${_variableName}}")
# endforeach()
#
# List imported targets from 'find_package(..)'
# get_property(importTargets DIRECTORY "${CMAKE_SOURCE_DIR}" PROPERTY IMPORTED_TARGETS)
# message("Import Targets: ${importTargets}")
#
# Build this target to test generator expressions
# add_custom_target(debug_gen_expr COMMAND ${CMAKE_COMMAND} -E echo "$<IF:$<BOOL:${SPACEGAME_BUILD_SHADERS}>,true_string,false_string>")

14
README.md Normal file
View File

@@ -0,0 +1,14 @@
# Spacegame
My little toy voxel engine born from an unconventional use of the rendering pipeline.
An idea that just kept working until I had a small prototype.
Might become a real game one day when it grows up.
## Experiements here include
- CMake + ninja as a build system
- The entire llvm compiler suite + cross compiling linux->windows
- A little Orthodox C++
- Developing my own camera and input system
- Vulkan via bgfx: compute shader to generate geometry on the GPU + rendering the result.
- Automated release builds with docker + CI

BIN
assets/textures/colors.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 300 B

18
cmake/README.md Normal file
View File

@@ -0,0 +1,18 @@
# Toolchain
To use the same toolchain as the CI in vscode
place the file 'cmake-kits.json' into your '.vscode' directory.
Then select the 'Spacegame LLVM Kit' in the cmake-tools extension.
Restarting vscode may be necessary.
# Debugging
To enable debugging add this to your .vscode/settings.json
```
"cmake.debugConfig": {
"MIMode": "gdb",
"miDebuggerPath": "/usr/bin/gdb"
}
```
# Usefull links
- [LLVM Debian/Ubuntu packages](https://apt.llvm.org/)

View File

@@ -0,0 +1,34 @@
# This toolchain configuration file can be used cross-compile from linux to windows
# Ref.: https://github.com/glfw/glfw/blob/master/CMake/x86_64-w64-mingw32-clang.cmake
MESSAGE("Cross-Compiling with toolchain file: ${CMAKE_TOOLCHAIN_FILE}")
# llvm-mingw-x86_64 is expected to be avilable under this directory
set(MINGW_DIRECTORY /llvm-mingw-x86_64)
# Define the environment for cross-compiling with 64-bit MinGW-w64 Clang
set(CMAKE_SYSTEM_NAME Windows)
SET(CMAKE_SYSTEM_VERSION 1)
set(CMAKE_C_COMPILER ${MINGW_DIRECTORY}/bin/x86_64-w64-mingw32-clang)
set(CMAKE_CXX_COMPILER ${MINGW_DIRECTORY}/bin/x86_64-w64-mingw32-clang++)
set(CMAKE_RC_COMPILER ${MINGW_DIRECTORY}/bin/x86_64-w64-mingw32-windres)
set(CMAKE_RANLIB ${MINGW_DIRECTORY}/bin/x86_64-w64-mingw32-ranlib)
set(CMAKE_AR ${MINGW_DIRECTORY}/bin/x86_64-w64-mingw32-ar)
set(CMAKE_STRIP ${MINGW_DIRECTORY}/bin/x86_64-w64-mingw32-strip)
# Configure the behaviour of the find commands
set(CMAKE_FIND_ROOT_PATH
${MINGW_DIRECTORY}/x86_64-w64-mingw32
${MINGW_DIRECTORY}
/spacegame_deps)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
# Note: For windows we supply all libraries in use directly through mingw => Full static linking!
set(CMAKE_CXX_FLAGS_INIT "-stdlib=libc++")
set(CMAKE_EXE_LINKER_FLAGS_INIT "-fuse-ld=lld -static")
set(CMAKE_MODULE_LINKER_FLAGS_INIT "-fuse-ld=lld -static")
set(CMAKE_SHARED_LINKER_FLAGS_INIT "-fuse-ld=lld -static")
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE TRUE)
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_MINSIZEREL TRUE)

View File

@@ -0,0 +1,23 @@
# This toolchain configuration file can be used for 'normal' linux native compilation
MESSAGE("Compiling with toolchain file: ${CMAKE_TOOLCHAIN_FILE}")
# Define the environment for compiling with 64-bit clang
# Note: Ensure the proper links / aliases are set
# ex. /usr/bin/clang -> /usr/bin/clang-18
set(CMAKE_C_COMPILER /usr/bin/clang )
set(CMAKE_CXX_COMPILER /usr/bin/clang++ )
set(CMAKE_RC_COMPILER /usr/bin/llvm-windres)
set(CMAKE_RANLIB /usr/bin/llvm-ranlib )
set(CMAKE_AR /usr/bin/llvm-ar )
set(CMAKE_STRIP /usr/bin/llvm-strip )
# Note: We can static link with libc++, but not entirely since we depend on dynamic
# libraries such as x11,glx,etc...
# Note: Despite its name '-static-libstdc++' the option causes libc++ to be linked statically
set(CMAKE_CXX_FLAGS_INIT "-stdlib=libc++" )
set(CMAKE_EXE_LINKER_FLAGS_INIT "-fuse-ld=lld -static-libstdc++")
set(CMAKE_MODULE_LINKER_FLAGS_INIT "-fuse-ld=lld -static-libstdc++")
set(CMAKE_SHARED_LINKER_FLAGS_INIT "-fuse-ld=lld -static-libstdc++")
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE TRUE)
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_MINSIZEREL TRUE)

10
cmake/cmake-kits.json Normal file
View File

@@ -0,0 +1,10 @@
[
{
"name": "Spacegame LLVM Kit",
"toolchainFile": "cmake/clang_toolchain.cmake",
"cmakeSettings":
{
"SPACEGAME_BUILD_SHADERS": "ON"
}
}
]

37
docker/Dockerfile_Base Normal file
View File

@@ -0,0 +1,37 @@
# Ensure `nvidia-smi` & `vulkaninfo` run correctly on the host system.
# Run with `sudo docker run -it --rm --gpus all <tag>`.
# Check `nvidia-smi` & `vulkaninfo` run correctly inside the container.
# may need to install:
# libnvidia-gl-525-server \
# vulkan-tools
# for debugging maybe: gdb
FROM ubuntu:22.04
# Non interactive mode
ENV DEBIAN_FRONTEND=noninteractive
COPY docker/rebuild_from_base.stamp docker/rebuild_from_base.stamp
# Dependencies & Tools
RUN apt-get update && \
apt-get install -y --no-install-recommends \
cmake git micro jq \
pkg-config curl wget zip \
ca-certificates xz-utils \
software-properties-common \
cppcheck valgrind \
`# glfw dependecies for x11 ` \
xorg-dev \
`# glfw dependecies for wayland ` \
libwayland-dev libxkbcommon-dev wayland-protocols extra-cmake-modules \
`# bgfx dependecies ` \
libgl1-mesa-dev x11proto-core-dev libx11-dev && \
apt-get clean && \
rm -rf /var/lib/apt/lists/* && \
`# custom ninja ` \
wget -O /usr/bin/ninja https://git.lph.zone/crydsch/ninja/releases/download/latest.proc_loadavg/ninja && \
chmod +x /usr/bin/ninja && \
ninja --version && \
`# disable git detachedHead warning ` \
git config --global advice.detachedHead false

32
docker/Dockerfile_Linux Normal file
View File

@@ -0,0 +1,32 @@
FROM spacegame_base
COPY docker/rebuild_from_llvm.stamp docker/rebuild_from_llvm.stamp
# LLVM
# RUN `# llvm-16 ` \
# wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc && \
# add-apt-repository deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-16 main && \
# apt-get update && \
# apt-get install -y --no-install-recommends \
# clang-16 clang-tools-16 \
# lldb-16 lld-16 \
# clang-tidy-16 clang-format-16 \
# libc++-16-dev libc++abi-16-dev && \
# apt-get clean && \
# rm -rf /var/lib/apt/lists/*
COPY scripts/setup_llvm_links.sh scripts/setup_llvm_links.sh
RUN `# llvm stable via convenience script` \
wget https://apt.llvm.org/llvm.sh && \
chmod +x llvm.sh && \
./llvm.sh all && \
apt-get clean && \
rm -rf /var/lib/apt/lists/* && \
`# setup required links` \
scripts/setup_llvm_links.sh
# Build 3rdparty deps
COPY 3rdparty 3rdparty
COPY cmake/clang_toolchain.cmake cmake/clang_toolchain.cmake
COPY scripts/build_deps.sh scripts/build_deps.sh
RUN scripts/build_deps.sh "cmake/clang_toolchain.cmake" && \
rm -rf 3rdparty/glfw 3rdparty/bx 3rdparty/bimg 3rdparty/bgfx 3rdparty/bgfx.cmake

24
docker/Dockerfile_Windows Normal file
View File

@@ -0,0 +1,24 @@
FROM spacegame_base
COPY docker/rebuild_from_llvm.stamp docker/rebuild_from_llvm.stamp
# LLVM
RUN `# llvm mingw ` \
curl -L \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer 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" \
-H "X-GitHub-Api-Version: 2022-11-28" \
https://api.github.com/repos/mstorsjo/llvm-mingw/releases/latest | jq '.assets[] | select( .name | test("ucrt-ubuntu-20.04-x86_64") ).browser_download_url' | xargs wget -O llvm-mingw-x86_64.tar.xz && \
mkdir llvm-mingw-x86_64 && \
tar xvf llvm-mingw-x86_64.tar.xz --directory llvm-mingw-x86_64 --strip-components=1 && \
rm -rf llvm-mingw-x86_64.tar.xz \
llvm-mingw-x86_64/aarch64-w64-mingw32 \
llvm-mingw-x86_64/armv7-w64-mingw32 \
llvm-mingw-x86_64/i686-w64-mingw32
# Build 3rdparty deps
COPY 3rdparty 3rdparty
COPY cmake/clang_mingw_toolchain.cmake cmake/clang_mingw_toolchain.cmake
COPY scripts/build_deps.sh scripts/build_deps.sh
RUN scripts/build_deps.sh "cmake/clang_mingw_toolchain.cmake" && \
rm -rf 3rdparty/glfw 3rdparty/bx 3rdparty/bimg 3rdparty/bgfx 3rdparty/bgfx.cmake

View File

View File

@@ -0,0 +1 @@
1

16
scripts/clean_deps_n_build.sh Executable file
View File

@@ -0,0 +1,16 @@
#!/bin/bash
set -e
if [ "$EUID" -ne 0 ]
then echo "This script must be run as root"
exit 1
fi
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
pushd "$SCRIPT_DIR"/..
rm -rf /spacegame_deps build* assets/shaders/*.spv 3rdparty/glfw 3rdparty/bx 3rdparty/bimg 3rdparty/bgfx 3rdparty/bgfx.cmake
popd

53
scripts/release_game.sh Executable file
View File

@@ -0,0 +1,53 @@
#!/bin/bash
set -e
# Construct base version: '1.2.3-123+20230527T195630.811d543'
# Platform specific versions may add extra info: '1.2.3-123+20230527T195630.811d543.linux.x11'
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
pushd "$SCRIPT_DIR"/.. 2>&1 1>/dev/null
# Fetch all tags from remote (if something changed)
git fetch --tags
# Get current version from latest tag
VERSION_SHORT=$(git describe --tags --abbrev=0)
echo "Current Version: $VERSION_SHORT"
# Advance by one revision
# check if revision present
if [[ $VERSION_SHORT != *"-"* ]]; then
VERSION_SHORT="$VERSION_SHORT-0"
fi
# seperate base version from revision
BASE_VERSION=$(echo $VERSION_SHORT | sed -E 's~^(.*[.].*[.].*)-([0-9]+)~\1~I')
REVISION=$(echo $VERSION_SHORT | sed -E 's~^(.*[.].*[.].*)-([0-9]+)~\2~I')
# +1
REVISION=$(($REVISION+1)) || $(echo 1)
# Assemble new version
VERSION_SHORT="$BASE_VERSION-$REVISION"
read -p "Enter New Version [$VERSION_SHORT]: " VERSION_SHORT_INPUT
VERSION_SHORT=${VERSION_SHORT_INPUT:-$VERSION_SHORT}
# Collect extra build information
# CMAKE_VERSION=$(grep CMAKE_PROJECT_VERSION:STATIC build/CMakeCache.txt | cut -d "=" -f2)
TIME=$(date -u +"%Y%m%dT%H%M%S")
COMMIT=$(git rev-parse HEAD)
VERSION_LONG="$VERSION_SHORT+$TIME.$COMMIT"
# Push any outstanding commits
git commit --allow-empty -m "Release $VERSION_SHORT" -m "[CI SKIP]"
git push
# Create annotated tag named named after short version and annotated with long version
git tag -a -m "$VERSION_LONG" "$VERSION_SHORT"
# push the new tag
git push origin "$VERSION_SHORT"
popd 2>&1 1>/dev/null

58
scripts/setup_llvm_links.sh Executable file
View File

@@ -0,0 +1,58 @@
#!/bin/bash
# This cript creates default links to a set of llvm binaries
# ex. /usr/bin/clang -> /usr/bin/clang-18
set -e
if [ "$EUID" -ne 0 ]
then echo "This script must be run as root"
exit 1
fi
# Detect clang version
# Command Breakdown:
# Get all installed packages
# | Detect clang version
# | | Sort available versions
# | | | Pick highest version
# | | | | Remove "clang-" to extract the version only
# v v v v v
VER=$(dpkg --list | grep -o "clang-[0-9][0-9]*" | sort -r | head -n 1 | sed "s#clang-##")
#echo Detected Tool-Version: $VER
# Ensure all tools are installed
TOOLS=(
clang
clang++
llvm-windres
llvm-ranlib
llvm-ar
llvm-strip
)
for TOOL in "${TOOLS[@]}"
do
set +e
OUTPUT=$($TOOL-$VER --version 2>&1)
EXIT_CODE=$?
set -e
# on error (ex. no ground truth file) just print nothing
if [ $EXIT_CODE -ne 0 ]
then
echo $TOOL-$VER must be installed!
exit 1
fi
#echo Found $TOOL-$VER
# Get full path
TOOL_PATH=$(which $TOOL-$VER)
# Create links
ln -sf $TOOL_PATH /usr/bin/$TOOL
done

106
shaders/CMakeLists.txt Normal file
View File

@@ -0,0 +1,106 @@
# Ref.: https://stackoverflow.com/questions/67040258/cmake-compile-glsl-shaders
# Ref.: https://cmake.org/cmake/help/latest/command/add_custom_command.html
set(SHADER_SOURCE_DIR ${PROJECT_SOURCE_DIR}/shaders)
set(SHADER_BIN_DIR ${PROJECT_SOURCE_DIR}/assets/shaders)
file(GLOB VERTEX_SHADERS ${SHADER_SOURCE_DIR}/vs_*.sc)
file(GLOB FRAGMENT_SHADERS ${SHADER_SOURCE_DIR}/fs_*.sc)
file(GLOB COMPUTE_SHADERS ${SHADER_SOURCE_DIR}/cs_*.sc)
# vertex shaders
foreach(SHADER_IN IN LISTS VERTEX_SHADERS)
cmake_path(GET SHADER_IN FILENAME SHADER_NAME) # ex. SHADER_NAME==vs_cubes.sc
# output file
string(REPLACE ".sc" ".spv" SHADER_OUT ${SHADER_NAME})
set(SHADER_OUT ${SHADER_BIN_DIR}/${SHADER_OUT})
# varying file
string(REPLACE "vs" "varying" SHADER_VARYING ${SHADER_IN})
# message("SHADER_IN: " ${SHADER_IN} " - SHADER_VARYING: " ${SHADER_VARYING} " - SHADER_OUT: " ${SHADER_OUT})
_bgfx_shaderc_parse(CLI
FILE ${SHADER_IN}
OUTPUT ${SHADER_OUT}
VERTEX
LINUX
PROFILE spirv
VARYINGDEF ${SHADER_VARYING}
INCLUDES ${SHADER_BIN_DIR}
)
add_custom_command(
OUTPUT ${SHADER_OUT}
COMMAND ${SHADERC} ${CLI}
DEPENDS ${SHADER_IN}
COMMENT "Compiling shader ${SHADER_NAME}"
)
list(APPEND SPV_SHADERS ${SHADER_OUT})
endforeach()
# fragment shaders
foreach(SHADER_IN IN LISTS FRAGMENT_SHADERS)
cmake_path(GET SHADER_IN FILENAME SHADER_NAME) # ex. SHADER_NAME==vs_cubes.sc
# output file
string(REPLACE ".sc" ".spv" SHADER_OUT ${SHADER_NAME})
set(SHADER_OUT ${SHADER_BIN_DIR}/${SHADER_OUT})
# varying file
string(REPLACE "fs" "varying" SHADER_VARYING ${SHADER_IN})
# message("SHADER_IN: " ${SHADER_IN} " - SHADER_VARYING: " ${SHADER_VARYING} " - SHADER_OUT: " ${SHADER_OUT})
_bgfx_shaderc_parse(CLI
FILE ${SHADER_IN}
OUTPUT ${SHADER_OUT}
FRAGMENT
LINUX
PROFILE spirv
VARYINGDEF ${SHADER_VARYING}
INCLUDES ${SHADER_BIN_DIR}
)
add_custom_command(
OUTPUT ${SHADER_OUT}
COMMAND ${SHADERC} ${CLI}
DEPENDS ${SHADER_IN}
COMMENT "Compiling shader ${SHADER_NAME}"
)
list(APPEND SPV_SHADERS ${SHADER_OUT})
endforeach()
# compute shaders
foreach(SHADER_IN IN LISTS COMPUTE_SHADERS)
cmake_path(GET SHADER_IN FILENAME SHADER_NAME) # ex. SHADER_NAME==vs_cubes.sc
# output file
string(REPLACE ".sc" ".spv" SHADER_OUT ${SHADER_NAME})
set(SHADER_OUT ${SHADER_BIN_DIR}/${SHADER_OUT})
# message("SHADER_IN: " ${SHADER_IN} " - "SHADER_OUT: " ${SHADER_OUT})
_bgfx_shaderc_parse(CLI
FILE ${SHADER_IN}
OUTPUT ${SHADER_OUT}
COMPUTE
LINUX
PROFILE spirv
INCLUDES ${SHADER_BIN_DIR}
)
add_custom_command(
OUTPUT ${SHADER_OUT}
COMMAND ${SHADERC} ${CLI}
DEPENDS ${SHADER_IN}
COMMENT "Compiling shader ${SHADER_NAME}"
)
list(APPEND SPV_SHADERS ${SHADER_OUT})
endforeach()
add_custom_target(shaders ALL DEPENDS ${SPV_SHADERS})

327
shaders/bgfx_compute.sh Normal file
View File

@@ -0,0 +1,327 @@
/*
* Copyright 2011-2022 Branimir Karadzic. All rights reserved.
* License: https://github.com/bkaradzic/bgfx/blob/master/LICENSE
*/
#ifndef BGFX_COMPUTE_H_HEADER_GUARD
#define BGFX_COMPUTE_H_HEADER_GUARD
#include "bgfx_shader.sh"
#ifndef __cplusplus
#if BGFX_SHADER_LANGUAGE_HLSL > 0 && BGFX_SHADER_LANGUAGE_HLSL < 400
# error "Compute is not supported!"
#endif // BGFX_SHADER_LANGUAGE_HLSL
#if BGFX_SHADER_LANGUAGE_METAL || BGFX_SHADER_LANGUAGE_SPIRV
# define FORMAT(_format) [[spv::format_ ## _format]]
# define WRITEONLY [[spv::nonreadable]]
#else
# define FORMAT(_format)
# define WRITEONLY
#endif // BGFX_SHADER_LANGUAGE_METAL || BGFX_SHADER_LANGUAGE_SPIRV
#if BGFX_SHADER_LANGUAGE_GLSL
#define SHARED shared
#define __IMAGE_XX(_name, _format, _reg, _image, _access) \
layout(_format, binding=_reg) _access uniform highp _image _name
#define readwrite
#define IMAGE2D_RO( _name, _format, _reg) __IMAGE_XX(_name, _format, _reg, image2D, readonly)
#define UIMAGE2D_RO(_name, _format, _reg) __IMAGE_XX(_name, _format, _reg, uimage2D, readonly)
#define IMAGE2D_WR( _name, _format, _reg) __IMAGE_XX(_name, _format, _reg, image2D, writeonly)
#define UIMAGE2D_WR(_name, _format, _reg) __IMAGE_XX(_name, _format, _reg, uimage2D, writeonly)
#define IMAGE2D_RW( _name, _format, _reg) __IMAGE_XX(_name, _format, _reg, image2D, readwrite)
#define UIMAGE2D_RW(_name, _format, _reg) __IMAGE_XX(_name, _format, _reg, uimage2D, readwrite)
#define IMAGE2D_ARRAY_RO( _name, _format, _reg) __IMAGE_XX(_name, _format, _reg, image2DArray, readonly)
#define UIMAGE2D_ARRAY_RO(_name, _format, _reg) __IMAGE_XX(_name, _format, _reg, uimage2DArray, readonly)
#define IMAGE2D_ARRAY_WR( _name, _format, _reg) __IMAGE_XX(_name, _format, _reg, image2DArray, writeonly)
#define UIMAGE2D_ARRAY_WR(_name, _format, _reg) __IMAGE_XX(_name, _format, _reg, uimage2DArray, writeonly)
#define IMAGE2D_ARRAY_RW( _name, _format, _reg) __IMAGE_XX(_name, _format, _reg, image2DArray, readwrite)
#define UIMAGE2D_ARRAY_RW(_name, _format, _reg) __IMAGE_XX(_name, _format, _reg, uimage2DArray, readwrite)
#define IMAGE3D_RO( _name, _format, _reg) __IMAGE_XX(_name, _format, _reg, image3D, readonly)
#define UIMAGE3D_RO(_name, _format, _reg) __IMAGE_XX(_name, _format, _reg, uimage3D, readonly)
#define IMAGE3D_WR( _name, _format, _reg) __IMAGE_XX(_name, _format, _reg, image3D, writeonly)
#define UIMAGE3D_WR(_name, _format, _reg) __IMAGE_XX(_name, _format, _reg, uimage3D, writeonly)
#define IMAGE3D_RW( _name, _format, _reg) __IMAGE_XX(_name, _format, _reg, image3D, readwrite)
#define UIMAGE3D_RW(_name, _format, _reg) __IMAGE_XX(_name, _format, _reg, uimage3D, readwrite)
#define __BUFFER_XX(_name, _type, _reg, _access) \
layout(std430, binding=_reg) _access buffer _name ## Buffer \
{ \
_type _name[]; \
}
#define BUFFER_RO(_name, _type, _reg) __BUFFER_XX(_name, _type, _reg, readonly)
#define BUFFER_RW(_name, _type, _reg) __BUFFER_XX(_name, _type, _reg, readwrite)
#define BUFFER_WR(_name, _type, _reg) __BUFFER_XX(_name, _type, _reg, writeonly)
#define NUM_THREADS(_x, _y, _z) layout (local_size_x = _x, local_size_y = _y, local_size_z = _z) in;
#define atomicFetchAndAdd(_mem, _data, _original) _original = atomicAdd(_mem, _data)
#define atomicFetchAndAnd(_mem, _data, _original) _original = atomicAnd(_mem, _data)
#define atomicFetchAndMax(_mem, _data, _original) _original = atomicMax(_mem, _data)
#define atomicFetchAndMin(_mem, _data, _original) _original = atomicMin(_mem, _data)
#define atomicFetchAndOr(_mem, _data, _original) _original = atomicOr(_mem, _data)
#define atomicFetchAndXor(_mem, _data, _original) _original = atomicXor(_mem, _data)
#define atomicFetchAndExchange(_mem, _data, _original) _original = atomicExchange(_mem, _data)
#define atomicFetchCompareExchange(_mem, _compare, _data, _original) _original = atomicCompSwap(_mem,_compare, _data)
#else
#define SHARED groupshared
#define COMP_r32ui uint
#define COMP_rg32ui uint2
#define COMP_rgba32ui uint4
#define COMP_r32f float
#define COMP_r16f float
#define COMP_rg16f float2
#define COMP_rgba16f float4
#if BGFX_SHADER_LANGUAGE_HLSL
# define COMP_rgba8 unorm float4
# define COMP_rg8 unorm float2
# define COMP_r8 unorm float
#else
# define COMP_rgba8 float4
# define COMP_rg8 float2
# define COMP_r8 float
#endif // BGFX_SHADER_LANGUAGE_HLSL
#define COMP_rgba32f float4
#define IMAGE2D_RO( _name, _format, _reg) \
FORMAT(_format) Texture2D<COMP_ ## _format> _name : REGISTER(t, _reg); \
#define UIMAGE2D_RO(_name, _format, _reg) IMAGE2D_RO(_name, _format, _reg)
#define IMAGE2D_WR( _name, _format, _reg) \
WRITEONLY FORMAT(_format) RWTexture2D<COMP_ ## _format> _name : REGISTER(u, _reg); \
#define UIMAGE2D_WR(_name, _format, _reg) IMAGE2D_WR(_name, _format, _reg)
#define IMAGE2D_RW( _name, _format, _reg) \
FORMAT(_format) RWTexture2D<COMP_ ## _format> _name : REGISTER(u, _reg); \
#define UIMAGE2D_RW(_name, _format, _reg) IMAGE2D_RW(_name, _format, _reg)
#define IMAGE2D_ARRAY_RO(_name, _format, _reg) \
FORMAT(_format) Texture2DArray<COMP_ ## _format> _name : REGISTER(t, _reg); \
#define UIMAGE2D_ARRAY_RO(_name, _format, _reg) IMAGE2D_ARRAY_RO(_name, _format, _reg)
#define IMAGE2D_ARRAY_WR( _name, _format, _reg) \
WRITEONLY FORMAT(_format) RWTexture2DArray<COMP_ ## _format> _name : REGISTER(u, _reg); \
#define UIMAGE2D_ARRAY_WR(_name, _format, _reg) IMAGE2D_ARRAY_WR(_name, _format, _reg)
#define IMAGE2D_ARRAY_RW(_name, _format, _reg) \
FORMAT(_format) RWTexture2DArray<COMP_ ## _format> _name : REGISTER(u, _reg); \
#define UIMAGE2D_ARRAY_RW(_name, _format, _reg) IMAGE2D_ARRAY_RW(_name, _format, _reg)
#define IMAGE3D_RO( _name, _format, _reg) \
FORMAT(_format) Texture3D<COMP_ ## _format> _name : REGISTER(t, _reg);
#define UIMAGE3D_RO(_name, _format, _reg) IMAGE3D_RO(_name, _format, _reg)
#define IMAGE3D_WR( _name, _format, _reg) \
WRITEONLY FORMAT(_format) RWTexture3D<COMP_ ## _format> _name : REGISTER(u, _reg);
#define UIMAGE3D_WR(_name, _format, _reg) IMAGE3D_RW(_name, _format, _reg)
#define IMAGE3D_RW( _name, _format, _reg) \
FORMAT(_format) RWTexture3D<COMP_ ## _format> _name : REGISTER(u, _reg); \
#define UIMAGE3D_RW(_name, _format, _reg) IMAGE3D_RW(_name, _format, _reg)
#if BGFX_SHADER_LANGUAGE_METAL || BGFX_SHADER_LANGUAGE_SPIRV
#define BUFFER_RO(_name, _struct, _reg) StructuredBuffer<_struct> _name : REGISTER(t, _reg)
#define BUFFER_RW(_name, _struct, _reg) RWStructuredBuffer <_struct> _name : REGISTER(u, _reg)
#define BUFFER_WR(_name, _struct, _reg) BUFFER_RW(_name, _struct, _reg)
#else
#define BUFFER_RO(_name, _struct, _reg) Buffer<_struct> _name : REGISTER(t, _reg)
#define BUFFER_RW(_name, _struct, _reg) RWBuffer<_struct> _name : REGISTER(u, _reg)
#define BUFFER_WR(_name, _struct, _reg) BUFFER_RW(_name, _struct, _reg)
#endif
#define NUM_THREADS(_x, _y, _z) [numthreads(_x, _y, _z)]
#define __IMAGE_IMPL_A(_format, _storeComponents, _type, _loadComponents) \
_type imageLoad(Texture2D<_format> _image, ivec2 _uv) \
{ \
return _image[_uv]._loadComponents; \
} \
\
ivec2 imageSize(Texture2D<_format> _image) \
{ \
uvec2 result; \
_image.GetDimensions(result.x, result.y); \
return ivec2(result); \
} \
\
_type imageLoad(RWTexture2D<_format> _image, ivec2 _uv) \
{ \
return _image[_uv]._loadComponents; \
} \
\
void imageStore(RWTexture2D<_format> _image, ivec2 _uv, _type _value) \
{ \
_image[_uv] = _value._storeComponents; \
} \
\
ivec2 imageSize(RWTexture2D<_format> _image) \
{ \
uvec2 result; \
_image.GetDimensions(result.x, result.y); \
return ivec2(result); \
} \
\
_type imageLoad(Texture2DArray<_format> _image, ivec3 _uvw) \
{ \
return _image[_uvw]._loadComponents; \
} \
\
ivec3 imageSize(Texture2DArray<_format> _image) \
{ \
uvec3 result; \
_image.GetDimensions(result.x, result.y, result.z); \
return ivec3(result); \
} \
\
_type imageLoad(RWTexture2DArray<_format> _image, ivec3 _uvw) \
{ \
return _image[_uvw]._loadComponents; \
} \
\
void imageStore(RWTexture2DArray<_format> _image, ivec3 _uvw, _type _value) \
{ \
_image[_uvw] = _value._storeComponents; \
} \
\
ivec3 imageSize(RWTexture2DArray<_format> _image) \
{ \
uvec3 result; \
_image.GetDimensions(result.x, result.y, result.z); \
return ivec3(result); \
} \
\
_type imageLoad(Texture3D<_format> _image, ivec3 _uvw) \
{ \
return _image[_uvw]._loadComponents; \
} \
\
ivec3 imageSize(Texture3D<_format> _image) \
{ \
uvec3 result; \
_image.GetDimensions(result.x, result.y, result.z); \
return ivec3(result); \
} \
\
_type imageLoad(RWTexture3D<_format> _image, ivec3 _uvw) \
{ \
return _image[_uvw]._loadComponents; \
} \
\
void imageStore(RWTexture3D<_format> _image, ivec3 _uvw, _type _value) \
{ \
_image[_uvw] = _value._storeComponents; \
} \
\
ivec3 imageSize(RWTexture3D<_format> _image) \
{ \
uvec3 result; \
_image.GetDimensions(result.x, result.y, result.z); \
return ivec3(result); \
}
#define __IMAGE_IMPL_ATOMIC(_format, _storeComponents, _type, _loadComponents) \
\
void imageAtomicAdd(RWTexture2D<_format> _image, ivec2 _uv, _type _value) \
{ \
InterlockedAdd(_image[_uv], _value._storeComponents); \
} \
__IMAGE_IMPL_A(float, x, vec4, xxxx)
__IMAGE_IMPL_A(float2, xy, vec4, xyyy)
__IMAGE_IMPL_A(float4, xyzw, vec4, xyzw)
__IMAGE_IMPL_A(uint, x, uvec4, xxxx)
__IMAGE_IMPL_A(uint2, xy, uvec4, xyyy)
__IMAGE_IMPL_A(uint4, xyzw, uvec4, xyzw)
#if BGFX_SHADER_LANGUAGE_HLSL
__IMAGE_IMPL_A(unorm float, x, vec4, xxxx)
__IMAGE_IMPL_A(unorm float2, xy, vec4, xyyy)
__IMAGE_IMPL_A(unorm float4, xyzw, vec4, xyzw)
#endif
__IMAGE_IMPL_ATOMIC(uint, x, uvec4, xxxx)
#define atomicAdd(_mem, _data) InterlockedAdd(_mem, _data)
#define atomicAnd(_mem, _data) InterlockedAnd(_mem, _data)
#define atomicMax(_mem, _data) InterlockedMax(_mem, _data)
#define atomicMin(_mem, _data) InterlockedMin(_mem, _data)
#define atomicOr(_mem, _data) InterlockedOr(_mem, _data)
#define atomicXor(_mem, _data) InterlockedXor(_mem, _data)
#define atomicFetchAndAdd(_mem, _data, _original) InterlockedAdd(_mem, _data, _original)
#define atomicFetchAndAnd(_mem, _data, _original) InterlockedAnd(_mem, _data, _original)
#define atomicFetchAndMax(_mem, _data, _original) InterlockedMax(_mem, _data, _original)
#define atomicFetchAndMin(_mem, _data, _original) InterlockedMin(_mem, _data, _original)
#define atomicFetchAndOr(_mem, _data, _original) InterlockedOr(_mem, _data, _original)
#define atomicFetchAndXor(_mem, _data, _original) InterlockedXor(_mem, _data, _original)
#define atomicFetchAndExchange(_mem, _data, _original) InterlockedExchange(_mem, _data, _original)
#define atomicFetchCompareExchange(_mem, _compare, _data, _original) InterlockedCompareExchange(_mem,_compare, _data, _original)
// InterlockedCompareStore
#define barrier() GroupMemoryBarrierWithGroupSync()
#define memoryBarrier() GroupMemoryBarrierWithGroupSync()
#define memoryBarrierAtomicCounter() GroupMemoryBarrierWithGroupSync()
#define memoryBarrierBuffer() AllMemoryBarrierWithGroupSync()
#define memoryBarrierImage() GroupMemoryBarrierWithGroupSync()
#define memoryBarrierShared() GroupMemoryBarrierWithGroupSync()
#define groupMemoryBarrier() GroupMemoryBarrierWithGroupSync()
#endif // BGFX_SHADER_LANGUAGE_GLSL
#define dispatchIndirect( \
_buffer \
, _offset \
, _numX \
, _numY \
, _numZ \
) \
_buffer[(_offset)*2+0] = uvec4(_numX, _numY, _numZ, 0u)
#define drawIndirect( \
_buffer \
, _offset \
, _numVertices \
, _numInstances \
, _startVertex \
, _startInstance \
) \
_buffer[(_offset)*2+0] = uvec4(_numVertices, _numInstances, _startVertex, _startInstance)
#define drawIndexedIndirect( \
_buffer \
, _offset \
, _numIndices \
, _numInstances \
, _startIndex \
, _startVertex \
, _startInstance \
) \
_buffer[(_offset)*2+0] = uvec4(_numIndices, _numInstances, _startIndex, _startVertex); \
_buffer[(_offset)*2+1] = uvec4(_startInstance, 0u, 0u, 0u)
#endif // __cplusplus
#endif // BGFX_COMPUTE_H_HEADER_GUARD

698
shaders/bgfx_shader.sh Normal file
View File

@@ -0,0 +1,698 @@
/*
* Copyright 2011-2022 Branimir Karadzic. All rights reserved.
* License: https://github.com/bkaradzic/bgfx/blob/master/LICENSE
*/
#ifndef BGFX_SHADER_H_HEADER_GUARD
#define BGFX_SHADER_H_HEADER_GUARD
#if !defined(BGFX_CONFIG_MAX_BONES)
# define BGFX_CONFIG_MAX_BONES 32
#endif // !defined(BGFX_CONFIG_MAX_BONES)
#ifndef __cplusplus
#if BGFX_SHADER_LANGUAGE_HLSL > 300
# define BRANCH [branch]
# define LOOP [loop]
# define UNROLL [unroll]
#else
# define BRANCH
# define LOOP
# define UNROLL
#endif // BGFX_SHADER_LANGUAGE_HLSL > 300
#if (BGFX_SHADER_LANGUAGE_HLSL > 300 || BGFX_SHADER_LANGUAGE_METAL || BGFX_SHADER_LANGUAGE_SPIRV) && BGFX_SHADER_TYPE_FRAGMENT
# define EARLY_DEPTH_STENCIL [earlydepthstencil]
#else
# define EARLY_DEPTH_STENCIL
#endif // BGFX_SHADER_LANGUAGE_HLSL > 300 && BGFX_SHADER_TYPE_FRAGMENT
#if BGFX_SHADER_LANGUAGE_GLSL
# define ARRAY_BEGIN(_type, _name, _count) _type _name[_count] = _type[](
# define ARRAY_END() )
#else
# define ARRAY_BEGIN(_type, _name, _count) _type _name[_count] = {
# define ARRAY_END() }
#endif // BGFX_SHADER_LANGUAGE_GLSL
#if BGFX_SHADER_LANGUAGE_HLSL \
|| BGFX_SHADER_LANGUAGE_PSSL \
|| BGFX_SHADER_LANGUAGE_SPIRV \
|| BGFX_SHADER_LANGUAGE_METAL
# define CONST(_x) static const _x
# define dFdx(_x) ddx(_x)
# define dFdy(_y) ddy(-(_y))
# define inversesqrt(_x) rsqrt(_x)
# define fract(_x) frac(_x)
# define bvec2 bool2
# define bvec3 bool3
# define bvec4 bool4
// To be able to patch the uav registers on the DXBC SPDB Chunk (D3D11 renderer) the whitespaces around
// '_type[_reg]' are necessary. This only affects shaders with debug info (i.e., those that have the SPDB Chunk).
# if BGFX_SHADER_LANGUAGE_HLSL > 400 || BGFX_SHADER_LANGUAGE_PSSL || BGFX_SHADER_LANGUAGE_SPIRV || BGFX_SHADER_LANGUAGE_METAL
# define REGISTER(_type, _reg) register( _type[_reg] )
# else
# define REGISTER(_type, _reg) register(_type ## _reg)
# endif // BGFX_SHADER_LANGUAGE_HLSL
# if BGFX_SHADER_LANGUAGE_HLSL > 300 || BGFX_SHADER_LANGUAGE_PSSL || BGFX_SHADER_LANGUAGE_SPIRV || BGFX_SHADER_LANGUAGE_METAL
# if BGFX_SHADER_LANGUAGE_HLSL > 400 || BGFX_SHADER_LANGUAGE_PSSL || BGFX_SHADER_LANGUAGE_SPIRV || BGFX_SHADER_LANGUAGE_METAL
# define dFdxCoarse(_x) ddx_coarse(_x)
# define dFdxFine(_x) ddx_fine(_x)
# define dFdyCoarse(_y) ddy_coarse(-(_y))
# define dFdyFine(_y) ddy_fine(-(_y))
# endif // BGFX_SHADER_LANGUAGE_HLSL > 400
# if BGFX_SHADER_LANGUAGE_HLSL || BGFX_SHADER_LANGUAGE_SPIRV || BGFX_SHADER_LANGUAGE_METAL
float intBitsToFloat(int _x) { return asfloat(_x); }
vec2 intBitsToFloat(uint2 _x) { return asfloat(_x); }
vec3 intBitsToFloat(uint3 _x) { return asfloat(_x); }
vec4 intBitsToFloat(uint4 _x) { return asfloat(_x); }
# endif // BGFX_SHADER_LANGUAGE_HLSL || BGFX_SHADER_LANGUAGE_SPIRV || BGFX_SHADER_LANGUAGE_METAL
float uintBitsToFloat(uint _x) { return asfloat(_x); }
vec2 uintBitsToFloat(uint2 _x) { return asfloat(_x); }
vec3 uintBitsToFloat(uint3 _x) { return asfloat(_x); }
vec4 uintBitsToFloat(uint4 _x) { return asfloat(_x); }
uint floatBitsToUint(float _x) { return asuint(_x); }
uvec2 floatBitsToUint(vec2 _x) { return asuint(_x); }
uvec3 floatBitsToUint(vec3 _x) { return asuint(_x); }
uvec4 floatBitsToUint(vec4 _x) { return asuint(_x); }
int floatBitsToInt(float _x) { return asint(_x); }
ivec2 floatBitsToInt(vec2 _x) { return asint(_x); }
ivec3 floatBitsToInt(vec3 _x) { return asint(_x); }
ivec4 floatBitsToInt(vec4 _x) { return asint(_x); }
uint bitfieldReverse(uint _x) { return reversebits(_x); }
uint2 bitfieldReverse(uint2 _x) { return reversebits(_x); }
uint3 bitfieldReverse(uint3 _x) { return reversebits(_x); }
uint4 bitfieldReverse(uint4 _x) { return reversebits(_x); }
# if !BGFX_SHADER_LANGUAGE_SPIRV
uint packHalf2x16(vec2 _x)
{
return (f32tof16(_x.y)<<16) | f32tof16(_x.x);
}
vec2 unpackHalf2x16(uint _x)
{
return vec2(f16tof32(_x & 0xffff), f16tof32(_x >> 16) );
}
# endif // !BGFX_SHADER_LANGUAGE_SPIRV
struct BgfxSampler2D
{
SamplerState m_sampler;
Texture2D m_texture;
};
struct BgfxISampler2D
{
Texture2D<ivec4> m_texture;
};
struct BgfxUSampler2D
{
Texture2D<uvec4> m_texture;
};
struct BgfxSampler2DArray
{
SamplerState m_sampler;
Texture2DArray m_texture;
};
struct BgfxSampler2DShadow
{
SamplerComparisonState m_sampler;
Texture2D m_texture;
};
struct BgfxSampler2DArrayShadow
{
SamplerComparisonState m_sampler;
Texture2DArray m_texture;
};
struct BgfxSampler3D
{
SamplerState m_sampler;
Texture3D m_texture;
};
struct BgfxISampler3D
{
Texture3D<ivec4> m_texture;
};
struct BgfxUSampler3D
{
Texture3D<uvec4> m_texture;
};
struct BgfxSamplerCube
{
SamplerState m_sampler;
TextureCube m_texture;
};
struct BgfxSamplerCubeShadow
{
SamplerComparisonState m_sampler;
TextureCube m_texture;
};
struct BgfxSampler2DMS
{
Texture2DMS<vec4> m_texture;
};
vec4 bgfxTexture2D(BgfxSampler2D _sampler, vec2 _coord)
{
return _sampler.m_texture.Sample(_sampler.m_sampler, _coord);
}
vec4 bgfxTexture2DBias(BgfxSampler2D _sampler, vec2 _coord, float _bias)
{
return _sampler.m_texture.SampleBias(_sampler.m_sampler, _coord, _bias);
}
vec4 bgfxTexture2DLod(BgfxSampler2D _sampler, vec2 _coord, float _level)
{
return _sampler.m_texture.SampleLevel(_sampler.m_sampler, _coord, _level);
}
vec4 bgfxTexture2DLodOffset(BgfxSampler2D _sampler, vec2 _coord, float _level, ivec2 _offset)
{
return _sampler.m_texture.SampleLevel(_sampler.m_sampler, _coord, _level, _offset);
}
vec4 bgfxTexture2DProj(BgfxSampler2D _sampler, vec3 _coord)
{
vec2 coord = _coord.xy * rcp(_coord.z);
return _sampler.m_texture.Sample(_sampler.m_sampler, coord);
}
vec4 bgfxTexture2DProj(BgfxSampler2D _sampler, vec4 _coord)
{
vec2 coord = _coord.xy * rcp(_coord.w);
return _sampler.m_texture.Sample(_sampler.m_sampler, coord);
}
vec4 bgfxTexture2DGrad(BgfxSampler2D _sampler, vec2 _coord, vec2 _dPdx, vec2 _dPdy)
{
return _sampler.m_texture.SampleGrad(_sampler.m_sampler, _coord, _dPdx, _dPdy);
}
vec4 bgfxTexture2DArray(BgfxSampler2DArray _sampler, vec3 _coord)
{
return _sampler.m_texture.Sample(_sampler.m_sampler, _coord);
}
vec4 bgfxTexture2DArrayLod(BgfxSampler2DArray _sampler, vec3 _coord, float _lod)
{
return _sampler.m_texture.SampleLevel(_sampler.m_sampler, _coord, _lod);
}
vec4 bgfxTexture2DArrayLodOffset(BgfxSampler2DArray _sampler, vec3 _coord, float _level, ivec2 _offset)
{
return _sampler.m_texture.SampleLevel(_sampler.m_sampler, _coord, _level, _offset);
}
float bgfxShadow2D(BgfxSampler2DShadow _sampler, vec3 _coord)
{
return _sampler.m_texture.SampleCmpLevelZero(_sampler.m_sampler, _coord.xy, _coord.z);
}
float bgfxShadow2DProj(BgfxSampler2DShadow _sampler, vec4 _coord)
{
vec3 coord = _coord.xyz * rcp(_coord.w);
return _sampler.m_texture.SampleCmpLevelZero(_sampler.m_sampler, coord.xy, coord.z);
}
vec4 bgfxShadow2DArray(BgfxSampler2DArrayShadow _sampler, vec4 _coord)
{
return _sampler.m_texture.SampleCmpLevelZero(_sampler.m_sampler, _coord.xyz, _coord.w);
}
vec4 bgfxTexture3D(BgfxSampler3D _sampler, vec3 _coord)
{
return _sampler.m_texture.Sample(_sampler.m_sampler, _coord);
}
vec4 bgfxTexture3DLod(BgfxSampler3D _sampler, vec3 _coord, float _level)
{
return _sampler.m_texture.SampleLevel(_sampler.m_sampler, _coord, _level);
}
ivec4 bgfxTexture3D(BgfxISampler3D _sampler, vec3 _coord)
{
uvec3 size;
_sampler.m_texture.GetDimensions(size.x, size.y, size.z);
return _sampler.m_texture.Load(ivec4(_coord * size, 0) );
}
uvec4 bgfxTexture3D(BgfxUSampler3D _sampler, vec3 _coord)
{
uvec3 size;
_sampler.m_texture.GetDimensions(size.x, size.y, size.z);
return _sampler.m_texture.Load(ivec4(_coord * size, 0) );
}
vec4 bgfxTextureCube(BgfxSamplerCube _sampler, vec3 _coord)
{
return _sampler.m_texture.Sample(_sampler.m_sampler, _coord);
}
vec4 bgfxTextureCubeBias(BgfxSamplerCube _sampler, vec3 _coord, float _bias)
{
return _sampler.m_texture.SampleBias(_sampler.m_sampler, _coord, _bias);
}
vec4 bgfxTextureCubeLod(BgfxSamplerCube _sampler, vec3 _coord, float _level)
{
return _sampler.m_texture.SampleLevel(_sampler.m_sampler, _coord, _level);
}
float bgfxShadowCube(BgfxSamplerCubeShadow _sampler, vec4 _coord)
{
return _sampler.m_texture.SampleCmpLevelZero(_sampler.m_sampler, _coord.xyz, _coord.w);
}
vec4 bgfxTexelFetch(BgfxSampler2D _sampler, ivec2 _coord, int _lod)
{
return _sampler.m_texture.Load(ivec3(_coord, _lod) );
}
vec4 bgfxTexelFetchOffset(BgfxSampler2D _sampler, ivec2 _coord, int _lod, ivec2 _offset)
{
return _sampler.m_texture.Load(ivec3(_coord, _lod), _offset );
}
vec2 bgfxTextureSize(BgfxSampler2D _sampler, int _lod)
{
vec2 result;
_sampler.m_texture.GetDimensions(result.x, result.y);
return result;
}
vec2 bgfxTextureSize(BgfxISampler2D _sampler, int _lod)
{
vec2 result;
_sampler.m_texture.GetDimensions(result.x, result.y);
return result;
}
vec2 bgfxTextureSize(BgfxUSampler2D _sampler, int _lod)
{
vec2 result;
_sampler.m_texture.GetDimensions(result.x, result.y);
return result;
}
vec4 bgfxTextureGather0(BgfxSampler2D _sampler, vec2 _coord)
{
return _sampler.m_texture.GatherRed(_sampler.m_sampler, _coord);
}
vec4 bgfxTextureGather1(BgfxSampler2D _sampler, vec2 _coord)
{
return _sampler.m_texture.GatherGreen(_sampler.m_sampler, _coord);
}
vec4 bgfxTextureGather2(BgfxSampler2D _sampler, vec2 _coord)
{
return _sampler.m_texture.GatherBlue(_sampler.m_sampler, _coord);
}
vec4 bgfxTextureGather3(BgfxSampler2D _sampler, vec2 _coord)
{
return _sampler.m_texture.GatherAlpha(_sampler.m_sampler, _coord);
}
vec4 bgfxTextureGatherOffset0(BgfxSampler2D _sampler, vec2 _coord, ivec2 _offset)
{
return _sampler.m_texture.GatherRed(_sampler.m_sampler, _coord, _offset);
}
vec4 bgfxTextureGatherOffset1(BgfxSampler2D _sampler, vec2 _coord, ivec2 _offset)
{
return _sampler.m_texture.GatherGreen(_sampler.m_sampler, _coord, _offset);
}
vec4 bgfxTextureGatherOffset2(BgfxSampler2D _sampler, vec2 _coord, ivec2 _offset)
{
return _sampler.m_texture.GatherBlue(_sampler.m_sampler, _coord, _offset);
}
vec4 bgfxTextureGatherOffset3(BgfxSampler2D _sampler, vec2 _coord, ivec2 _offset)
{
return _sampler.m_texture.GatherAlpha(_sampler.m_sampler, _coord, _offset);
}
vec4 bgfxTextureGather0(BgfxSampler2DArray _sampler, vec3 _coord)
{
return _sampler.m_texture.GatherRed(_sampler.m_sampler, _coord);
}
vec4 bgfxTextureGather1(BgfxSampler2DArray _sampler, vec3 _coord)
{
return _sampler.m_texture.GatherGreen(_sampler.m_sampler, _coord);
}
vec4 bgfxTextureGather2(BgfxSampler2DArray _sampler, vec3 _coord)
{
return _sampler.m_texture.GatherBlue(_sampler.m_sampler, _coord);
}
vec4 bgfxTextureGather3(BgfxSampler2DArray _sampler, vec3 _coord)
{
return _sampler.m_texture.GatherAlpha(_sampler.m_sampler, _coord);
}
ivec4 bgfxTexelFetch(BgfxISampler2D _sampler, ivec2 _coord, int _lod)
{
return _sampler.m_texture.Load(ivec3(_coord, _lod) );
}
uvec4 bgfxTexelFetch(BgfxUSampler2D _sampler, ivec2 _coord, int _lod)
{
return _sampler.m_texture.Load(ivec3(_coord, _lod) );
}
vec4 bgfxTexelFetch(BgfxSampler2DMS _sampler, ivec2 _coord, int _sampleIdx)
{
return _sampler.m_texture.Load(_coord, _sampleIdx);
}
vec4 bgfxTexelFetch(BgfxSampler2DArray _sampler, ivec3 _coord, int _lod)
{
return _sampler.m_texture.Load(ivec4(_coord, _lod) );
}
vec4 bgfxTexelFetch(BgfxSampler3D _sampler, ivec3 _coord, int _lod)
{
return _sampler.m_texture.Load(ivec4(_coord, _lod) );
}
vec3 bgfxTextureSize(BgfxSampler3D _sampler, int _lod)
{
vec3 result;
_sampler.m_texture.GetDimensions(result.x, result.y, result.z);
return result;
}
# define SAMPLER2D(_name, _reg) \
uniform SamplerState _name ## Sampler : REGISTER(s, _reg); \
uniform Texture2D _name ## Texture : REGISTER(t, _reg); \
static BgfxSampler2D _name = { _name ## Sampler, _name ## Texture }
# define ISAMPLER2D(_name, _reg) \
uniform Texture2D<ivec4> _name ## Texture : REGISTER(t, _reg); \
static BgfxISampler2D _name = { _name ## Texture }
# define USAMPLER2D(_name, _reg) \
uniform Texture2D<uvec4> _name ## Texture : REGISTER(t, _reg); \
static BgfxUSampler2D _name = { _name ## Texture }
# define sampler2D BgfxSampler2D
# define texture2D(_sampler, _coord) bgfxTexture2D(_sampler, _coord)
# define texture2DBias(_sampler, _coord, _bias) bgfxTexture2DBias(_sampler, _coord, _bias)
# define texture2DLod(_sampler, _coord, _level) bgfxTexture2DLod(_sampler, _coord, _level)
# define texture2DLodOffset(_sampler, _coord, _level, _offset) bgfxTexture2DLodOffset(_sampler, _coord, _level, _offset)
# define texture2DProj(_sampler, _coord) bgfxTexture2DProj(_sampler, _coord)
# define texture2DGrad(_sampler, _coord, _dPdx, _dPdy) bgfxTexture2DGrad(_sampler, _coord, _dPdx, _dPdy)
# define SAMPLER2DARRAY(_name, _reg) \
uniform SamplerState _name ## Sampler : REGISTER(s, _reg); \
uniform Texture2DArray _name ## Texture : REGISTER(t, _reg); \
static BgfxSampler2DArray _name = { _name ## Sampler, _name ## Texture }
# define sampler2DArray BgfxSampler2DArray
# define texture2DArray(_sampler, _coord) bgfxTexture2DArray(_sampler, _coord)
# define texture2DArrayLod(_sampler, _coord, _lod) bgfxTexture2DArrayLod(_sampler, _coord, _lod)
# define texture2DArrayLodOffset(_sampler, _coord, _level, _offset) bgfxTexture2DArrayLodOffset(_sampler, _coord, _level, _offset)
# define SAMPLER2DMS(_name, _reg) \
uniform Texture2DMS<vec4> _name ## Texture : REGISTER(t, _reg); \
static BgfxSampler2DMS _name = { _name ## Texture }
# define sampler2DMS BgfxSampler2DMS
# define SAMPLER2DSHADOW(_name, _reg) \
uniform SamplerComparisonState _name ## SamplerComparison : REGISTER(s, _reg); \
uniform Texture2D _name ## Texture : REGISTER(t, _reg); \
static BgfxSampler2DShadow _name = { _name ## SamplerComparison, _name ## Texture }
# define sampler2DShadow BgfxSampler2DShadow
# define shadow2D(_sampler, _coord) bgfxShadow2D(_sampler, _coord)
# define shadow2DProj(_sampler, _coord) bgfxShadow2DProj(_sampler, _coord)
# define SAMPLER2DARRAYSHADOW(_name, _reg) \
uniform SamplerComparisonState _name ## SamplerComparison : REGISTER(s, _reg); \
uniform Texture2DArray _name ## Texture : REGISTER(t, _reg); \
static BgfxSampler2DArrayShadow _name = { _name ## SamplerComparison, _name ## Texture }
# define sampler2DArrayShadow BgfxSampler2DArrayShadow
# define shadow2DArray(_sampler, _coord) bgfxShadow2DArray(_sampler, _coord)
# define SAMPLER3D(_name, _reg) \
uniform SamplerState _name ## Sampler : REGISTER(s, _reg); \
uniform Texture3D _name ## Texture : REGISTER(t, _reg); \
static BgfxSampler3D _name = { _name ## Sampler, _name ## Texture }
# define ISAMPLER3D(_name, _reg) \
uniform Texture3D<ivec4> _name ## Texture : REGISTER(t, _reg); \
static BgfxISampler3D _name = { _name ## Texture }
# define USAMPLER3D(_name, _reg) \
uniform Texture3D<uvec4> _name ## Texture : REGISTER(t, _reg); \
static BgfxUSampler3D _name = { _name ## Texture }
# define sampler3D BgfxSampler3D
# define texture3D(_sampler, _coord) bgfxTexture3D(_sampler, _coord)
# define texture3DLod(_sampler, _coord, _level) bgfxTexture3DLod(_sampler, _coord, _level)
# define SAMPLERCUBE(_name, _reg) \
uniform SamplerState _name ## Sampler : REGISTER(s, _reg); \
uniform TextureCube _name ## Texture : REGISTER(t, _reg); \
static BgfxSamplerCube _name = { _name ## Sampler, _name ## Texture }
# define samplerCube BgfxSamplerCube
# define textureCube(_sampler, _coord) bgfxTextureCube(_sampler, _coord)
# define textureCubeBias(_sampler, _coord, _bias) bgfxTextureCubeBias(_sampler, _coord, _bias)
# define textureCubeLod(_sampler, _coord, _level) bgfxTextureCubeLod(_sampler, _coord, _level)
# define SAMPLERCUBESHADOW(_name, _reg) \
uniform SamplerComparisonState _name ## SamplerComparison : REGISTER(s, _reg); \
uniform TextureCube _name ## Texture : REGISTER(t, _reg); \
static BgfxSamplerCubeShadow _name = { _name ## SamplerComparison, _name ## Texture }
# define samplerCubeShadow BgfxSamplerCubeShadow
# define shadowCube(_sampler, _coord) bgfxShadowCube(_sampler, _coord)
# define texelFetch(_sampler, _coord, _lod) bgfxTexelFetch(_sampler, _coord, _lod)
# define texelFetchOffset(_sampler, _coord, _lod, _offset) bgfxTexelFetchOffset(_sampler, _coord, _lod, _offset)
# define textureSize(_sampler, _lod) bgfxTextureSize(_sampler, _lod)
# define textureGather(_sampler, _coord, _comp) bgfxTextureGather ## _comp(_sampler, _coord)
# define textureGatherOffset(_sampler, _coord, _offset, _comp) bgfxTextureGatherOffset ## _comp(_sampler, _coord, _offset)
# else
# define sampler2DShadow sampler2D
vec4 bgfxTexture2DProj(sampler2D _sampler, vec3 _coord)
{
return tex2Dproj(_sampler, vec4(_coord.xy, 0.0, _coord.z) );
}
vec4 bgfxTexture2DProj(sampler2D _sampler, vec4 _coord)
{
return tex2Dproj(_sampler, _coord);
}
float bgfxShadow2D(sampler2DShadow _sampler, vec3 _coord)
{
#if 0
float occluder = tex2D(_sampler, _coord.xy).x;
return step(_coord.z, occluder);
#else
return tex2Dproj(_sampler, vec4(_coord.xy, _coord.z, 1.0) ).x;
#endif // 0
}
float bgfxShadow2DProj(sampler2DShadow _sampler, vec4 _coord)
{
#if 0
vec3 coord = _coord.xyz * rcp(_coord.w);
float occluder = tex2D(_sampler, coord.xy).x;
return step(coord.z, occluder);
#else
return tex2Dproj(_sampler, _coord).x;
#endif // 0
}
# define SAMPLER2D(_name, _reg) uniform sampler2D _name : REGISTER(s, _reg)
# define SAMPLER2DMS(_name, _reg) uniform sampler2DMS _name : REGISTER(s, _reg)
# define texture2D(_sampler, _coord) tex2D(_sampler, _coord)
# define texture2DProj(_sampler, _coord) bgfxTexture2DProj(_sampler, _coord)
# define SAMPLER2DARRAY(_name, _reg) SAMPLER2D(_name, _reg)
# define texture2DArray(_sampler, _coord) texture2D(_sampler, (_coord).xy)
# define texture2DArrayLod(_sampler, _coord, _lod) texture2DLod(_sampler, _coord, _lod)
# define SAMPLER2DSHADOW(_name, _reg) uniform sampler2DShadow _name : REGISTER(s, _reg)
# define shadow2D(_sampler, _coord) bgfxShadow2D(_sampler, _coord)
# define shadow2DProj(_sampler, _coord) bgfxShadow2DProj(_sampler, _coord)
# define SAMPLER3D(_name, _reg) uniform sampler3D _name : REGISTER(s, _reg)
# define texture3D(_sampler, _coord) tex3D(_sampler, _coord)
# define SAMPLERCUBE(_name, _reg) uniform samplerCUBE _name : REGISTER(s, _reg)
# define textureCube(_sampler, _coord) texCUBE(_sampler, _coord)
# define texture2DLod(_sampler, _coord, _level) tex2Dlod(_sampler, vec4( (_coord).xy, 0.0, _level) )
# define texture2DGrad(_sampler, _coord, _dPdx, _dPdy) tex2Dgrad(_sampler, _coord, _dPdx, _dPdy)
# define texture3DLod(_sampler, _coord, _level) tex3Dlod(_sampler, vec4( (_coord).xyz, _level) )
# define textureCubeLod(_sampler, _coord, _level) texCUBElod(_sampler, vec4( (_coord).xyz, _level) )
# endif // BGFX_SHADER_LANGUAGE_HLSL > 300
vec3 instMul(vec3 _vec, mat3 _mtx) { return mul(_mtx, _vec); }
vec3 instMul(mat3 _mtx, vec3 _vec) { return mul(_vec, _mtx); }
vec4 instMul(vec4 _vec, mat4 _mtx) { return mul(_mtx, _vec); }
vec4 instMul(mat4 _mtx, vec4 _vec) { return mul(_vec, _mtx); }
bvec2 lessThan(vec2 _a, vec2 _b) { return _a < _b; }
bvec3 lessThan(vec3 _a, vec3 _b) { return _a < _b; }
bvec4 lessThan(vec4 _a, vec4 _b) { return _a < _b; }
bvec2 lessThanEqual(vec2 _a, vec2 _b) { return _a <= _b; }
bvec3 lessThanEqual(vec3 _a, vec3 _b) { return _a <= _b; }
bvec4 lessThanEqual(vec4 _a, vec4 _b) { return _a <= _b; }
bvec2 greaterThan(vec2 _a, vec2 _b) { return _a > _b; }
bvec3 greaterThan(vec3 _a, vec3 _b) { return _a > _b; }
bvec4 greaterThan(vec4 _a, vec4 _b) { return _a > _b; }
bvec2 greaterThanEqual(vec2 _a, vec2 _b) { return _a >= _b; }
bvec3 greaterThanEqual(vec3 _a, vec3 _b) { return _a >= _b; }
bvec4 greaterThanEqual(vec4 _a, vec4 _b) { return _a >= _b; }
bvec2 notEqual(vec2 _a, vec2 _b) { return _a != _b; }
bvec3 notEqual(vec3 _a, vec3 _b) { return _a != _b; }
bvec4 notEqual(vec4 _a, vec4 _b) { return _a != _b; }
bvec2 equal(vec2 _a, vec2 _b) { return _a == _b; }
bvec3 equal(vec3 _a, vec3 _b) { return _a == _b; }
bvec4 equal(vec4 _a, vec4 _b) { return _a == _b; }
float mix(float _a, float _b, float _t) { return lerp(_a, _b, _t); }
vec2 mix(vec2 _a, vec2 _b, vec2 _t) { return lerp(_a, _b, _t); }
vec3 mix(vec3 _a, vec3 _b, vec3 _t) { return lerp(_a, _b, _t); }
vec4 mix(vec4 _a, vec4 _b, vec4 _t) { return lerp(_a, _b, _t); }
float mod(float _a, float _b) { return _a - _b * floor(_a / _b); }
vec2 mod(vec2 _a, vec2 _b) { return _a - _b * floor(_a / _b); }
vec3 mod(vec3 _a, vec3 _b) { return _a - _b * floor(_a / _b); }
vec4 mod(vec4 _a, vec4 _b) { return _a - _b * floor(_a / _b); }
#else
# define CONST(_x) const _x
# define atan2(_x, _y) atan(_x, _y)
# define mul(_a, _b) ( (_a) * (_b) )
# define saturate(_x) clamp(_x, 0.0, 1.0)
# define SAMPLER2D(_name, _reg) uniform sampler2D _name
# define SAMPLER2DMS(_name, _reg) uniform sampler2DMS _name
# define SAMPLER3D(_name, _reg) uniform sampler3D _name
# define SAMPLERCUBE(_name, _reg) uniform samplerCube _name
# define SAMPLER2DSHADOW(_name, _reg) uniform sampler2DShadow _name
# define SAMPLER2DARRAY(_name, _reg) uniform sampler2DArray _name
# define SAMPLER2DMSARRAY(_name, _reg) uniform sampler2DMSArray _name
# define SAMPLERCUBEARRAY(_name, _reg) uniform samplerCubeArray _name
# define SAMPLER2DARRAYSHADOW(_name, _reg) uniform sampler2DArrayShadow _name
# define ISAMPLER2D(_name, _reg) uniform isampler2D _name
# define USAMPLER2D(_name, _reg) uniform usampler2D _name
# define ISAMPLER3D(_name, _reg) uniform isampler3D _name
# define USAMPLER3D(_name, _reg) uniform usampler3D _name
# define texture2DBias(_sampler, _coord, _bias) texture2D(_sampler, _coord, _bias)
# define textureCubeBias(_sampler, _coord, _bias) textureCube(_sampler, _coord, _bias)
# if BGFX_SHADER_LANGUAGE_GLSL >= 130
# define texture2D(_sampler, _coord) texture(_sampler, _coord)
# define texture2DArray(_sampler, _coord) texture(_sampler, _coord)
# define texture3D(_sampler, _coord) texture(_sampler, _coord)
# define textureCube(_sampler, _coord) texture(_sampler, _coord)
# define texture2DLod(_sampler, _coord, _lod) textureLod(_sampler, _coord, _lod)
# define texture2DLodOffset(_sampler, _coord, _lod, _offset) textureLodOffset(_sampler, _coord, _lod, _offset)
# endif // BGFX_SHADER_LANGUAGE_GLSL >= 130
vec3 instMul(vec3 _vec, mat3 _mtx) { return mul(_vec, _mtx); }
vec3 instMul(mat3 _mtx, vec3 _vec) { return mul(_mtx, _vec); }
vec4 instMul(vec4 _vec, mat4 _mtx) { return mul(_vec, _mtx); }
vec4 instMul(mat4 _mtx, vec4 _vec) { return mul(_mtx, _vec); }
float rcp(float _a) { return 1.0/_a; }
vec2 rcp(vec2 _a) { return vec2(1.0)/_a; }
vec3 rcp(vec3 _a) { return vec3(1.0)/_a; }
vec4 rcp(vec4 _a) { return vec4(1.0)/_a; }
#endif // BGFX_SHADER_LANGUAGE_*
vec2 vec2_splat(float _x) { return vec2(_x, _x); }
vec3 vec3_splat(float _x) { return vec3(_x, _x, _x); }
vec4 vec4_splat(float _x) { return vec4(_x, _x, _x, _x); }
#if BGFX_SHADER_LANGUAGE_GLSL >= 130 || BGFX_SHADER_LANGUAGE_HLSL || BGFX_SHADER_LANGUAGE_PSSL || BGFX_SHADER_LANGUAGE_SPIRV || BGFX_SHADER_LANGUAGE_METAL
uvec2 uvec2_splat(uint _x) { return uvec2(_x, _x); }
uvec3 uvec3_splat(uint _x) { return uvec3(_x, _x, _x); }
uvec4 uvec4_splat(uint _x) { return uvec4(_x, _x, _x, _x); }
#endif // BGFX_SHADER_LANGUAGE_GLSL >= 130 || BGFX_SHADER_LANGUAGE_HLSL || BGFX_SHADER_LANGUAGE_PSSL || BGFX_SHADER_LANGUAGE_SPIRV || BGFX_SHADER_LANGUAGE_METAL
mat4 mtxFromRows(vec4 _0, vec4 _1, vec4 _2, vec4 _3)
{
#if BGFX_SHADER_LANGUAGE_GLSL
return transpose(mat4(_0, _1, _2, _3) );
#else
return mat4(_0, _1, _2, _3);
#endif // BGFX_SHADER_LANGUAGE_GLSL
}
mat4 mtxFromCols(vec4 _0, vec4 _1, vec4 _2, vec4 _3)
{
#if BGFX_SHADER_LANGUAGE_GLSL
return mat4(_0, _1, _2, _3);
#else
return transpose(mat4(_0, _1, _2, _3) );
#endif // BGFX_SHADER_LANGUAGE_GLSL
}
mat3 mtxFromRows(vec3 _0, vec3 _1, vec3 _2)
{
#if BGFX_SHADER_LANGUAGE_GLSL
return transpose(mat3(_0, _1, _2) );
#else
return mat3(_0, _1, _2);
#endif // BGFX_SHADER_LANGUAGE_GLSL
}
mat3 mtxFromCols(vec3 _0, vec3 _1, vec3 _2)
{
#if BGFX_SHADER_LANGUAGE_GLSL
return mat3(_0, _1, _2);
#else
return transpose(mat3(_0, _1, _2) );
#endif // BGFX_SHADER_LANGUAGE_GLSL
}
#if BGFX_SHADER_LANGUAGE_GLSL
#define mtxFromRows3(_0, _1, _2) transpose(mat3(_0, _1, _2) )
#define mtxFromRows4(_0, _1, _2, _3) transpose(mat4(_0, _1, _2, _3) )
#define mtxFromCols3(_0, _1, _2) mat3(_0, _1, _2)
#define mtxFromCols4(_0, _1, _2, _3) mat4(_0, _1, _2, _3)
#else
#define mtxFromRows3(_0, _1, _2) mat3(_0, _1, _2)
#define mtxFromRows4(_0, _1, _2, _3) mat4(_0, _1, _2, _3)
#define mtxFromCols3(_0, _1, _2) transpose(mat3(_0, _1, _2) )
#define mtxFromCols4(_0, _1, _2, _3) transpose(mat4(_0, _1, _2, _3) )
#endif // BGFX_SHADER_LANGUAGE_GLSL
uniform vec4 u_viewRect;
uniform vec4 u_viewTexel;
uniform mat4 u_view;
uniform mat4 u_invView;
uniform mat4 u_proj;
uniform mat4 u_invProj;
uniform mat4 u_viewProj;
uniform mat4 u_invViewProj;
uniform mat4 u_model[BGFX_CONFIG_MAX_BONES];
uniform mat4 u_modelView;
uniform mat4 u_modelViewProj;
uniform vec4 u_alphaRef4;
#define u_alphaRef u_alphaRef4.x
#endif // __cplusplus
#endif // BGFX_SHADER_H_HEADER_GUARD

103
shaders/cs_cubes.sc Normal file
View File

@@ -0,0 +1,103 @@
#include "orientations.sh"
#include "bgfx_compute.sh"
// INPUT
// grids (matrices)
BUFFER_RO(grids, vec4, 0);
// chunks (grid_id, offset_x, _y, _z)
BUFFER_RO(chunks, vec4, 1);
// blocks (chunk_id, transform [pos in chunk, rotation], idx_buf_offset, num_indices)
BUFFER_RO(blocks, vec4, 2);
// block selection (visible blocks post-culling)
BUFFER_RO(block_selection, float, 3);
// OUTPUT
// indirect draw calls
BUFFER_WR(indirectBuffer, uvec4, 4);
// matrices for each instance
BUFFER_WR(instanceBuffer, vec4, 5);
uniform vec4 u_cubes_compute_params;
// Use 64*1*1 local threads
NUM_THREADS(64, 1, 1)
void main()
{
int tId = int(gl_GlobalInvocationID.x);
int numDrawItems = int(u_cubes_compute_params.w);
int numToDrawPerThread = numDrawItems/64 + 1;
int idxStart = tId*numToDrawPerThread;
int idxMax = min(numDrawItems, (tId+1)*numToDrawPerThread);
for (int k = idxStart; k < idxMax; k++) {
uint block_id = block_selection[k];
// get block data
uint b_chunk_id = blocks[block_id].x;
uint b_transform = blocks[block_id].y;
uint b_index_buf_offset = blocks[block_id].z;
uint b_num_indices = blocks[block_id].w;
// get chunk data
uint c_grid_id = chunks[b_chunk_id].x;
vec3 c_offset = chunks[b_chunk_id].yzw;
// get grid data
mat4 g_mtx = mtxFromRows(grids[c_grid_id*4 + 0], grids[c_grid_id*4 + 1], grids[c_grid_id*4 + 2], grids[c_grid_id*4 + 3]);
// calc block offset
// b_transform == [off_x | off_y | off_z | orientation]
uint b_orientation = b_transform & 0x1F; // 5 bit
vec3 b_offset;
b_offset.x = (b_transform >> 11) & 0x7; // 3 bit
b_offset.y = (b_transform >> 8) & 0x7; // 3 bit
b_offset.z = (b_transform >> 5) & 0x7; // 3 bit
b_offset = b_offset + (c_offset * 8);
// rotate block -> offset block -> apply g_mtx
// apply orientation
mat3 b_mtx_orientation = orientations[b_orientation];
// apply offset
mat4 b_mtx = mat4(b_mtx_orientation); // Note: glsl matrices are colum major
//mat4 b_mtx;
//b_mtx[0] = vec4(1,0,0,0);
//b_mtx[1] = vec4(0,1,0,0);
//b_mtx[2] = vec4(0,0,1,0);
b_mtx[3] = vec4(b_offset, 1);
// apply g_mtx
mat4 mtx_model = mul(b_mtx, g_mtx);
instanceBuffer[k*4+0] = mtx_model[0];
instanceBuffer[k*4+1] = mtx_model[1];
instanceBuffer[k*4+2] = mtx_model[2];
instanceBuffer[k*4+3] = mtx_model[3];
// Fill indirect buffer
drawIndexedIndirect(
// Target location params:
indirectBuffer, // target buffer
k, // index in buffer
// Draw call params:
b_num_indices, // number of indices for this draw call
1u, // number of instances for this draw call. You can disable this draw call by setting to zero
b_index_buf_offset, // offset in the index buffer
0, // offset in the vertex buffer. Note that you can use this to "re-index" sub-meshes - all indices in this draw will be decremented by this amount
k // offset in the instance buffer. If you are drawing more than 1 instance per call see "gpu driven rendering" for how to handle
);
}
}

78
shaders/fs_cubes.sc Normal file
View File

@@ -0,0 +1,78 @@
$input tex_coord, world_pos
#include "bgfx_shader.sh"
#include "shaderlib.sh"
//layout(early_fragment_tests) in;
SAMPLER2D(texture_atlas_sampler, 0);
uniform vec4 u_cubes_compute_params;
// Ref: https://en.wikipedia.org/wiki/Blinn%E2%80%93Phong_reflection_model
const float lightPower = 40.0;
void main()
{
// vec4 col = texture2D(texture_atlas_sampler, tex_coord);
// // if(col.r + col.g + col.b == 0.0) discard; // playing with transparency
// vec3 normal = normalize(cross(dFdy(world_pos), dFdx(world_pos))); // screen space surface normal
// //col = vec4(normal, 1.0f);
// const float ambient = 0.2;
// const vec3 light_pos = vec3(-5.0, 5.0, -5.0);
// const vec3 light_col = vec3(1.0, 1.0, 1.0);
// vec3 light_dir = light_pos - world_pos;
// float squared_distance = light_dir.x*light_dir.x + light_dir.y*light_dir.y + light_dir.z*light_dir.z;
// light_dir = normalize(light_dir);
// vec4 diffuse = vec4(max(dot(light_dir, normal), 0) * light_col, 1.0);
// col = col * ambient + col * diffuse;
// // Note: if gl_FrontFacing is making problems
// // => https://stackoverflow.com/questions/24375171/is-there-a-reliable-alternative-to-gl-frontfacing-in-a-fragment-shader
// //if (gl_FrontFacing) {
// // col = vec4(1.0,0.0,0.0,1.0);
// //}else{
// // col = vec4(0.0,1.0,0.0,1.0);
// //}
// Calc id_coords (in id_atlas, for id sampling)
// in [0;ID_SIZE], is basically int
// id_coord = floor(id_coord);
// id_coord = id_coord +
// TODO use modf and combine floor/fract
// ! flooring may not be needed anyway
// just sample id_atlas with tex_coord
// Texturing rework V2
const uint IA_WIDTH = 2*8; // id's per row
const uint IA_HEIGHT = 2*8; // id's per column
const float ID_STRIDE_X = 1.0f / IA_WIDTH;
const float ID_STRIDE_Y = 1.0f / IA_HEIGHT;
// Calc tex_coords (in tex_atlas, for texture sampling)
// in [0;1] in local texture space
tex_coord = tex_coord * vec2(IA_WIDTH, IA_HEIGHT);
tex_coord = fract(tex_coord);
// vec4 col;
// if (tex_coord.x < 0.5)
// {
// col = vec4(1.0, 0.0, 0.0, 1.0);
// }
// else
// {
// col = vec4(0.0, 1.0, 0.0, 1.0);
// }
vec4 col = vec4(tex_coord, 0.0, 1.0);
gl_FragColor = col;
}

9
shaders/fs_lines.sc Normal file
View File

@@ -0,0 +1,9 @@
#include "bgfx_shader.sh"
#include "shaderlib.sh"
uniform vec4 u_line_color;
void main()
{
gl_FragColor = u_line_color;
}

27
shaders/orientations.sh Normal file
View File

@@ -0,0 +1,27 @@
const mat3 orientations[24] =
{
{{1.000000, 0.000000, 0.000000}, {0.000000, 1.000000, 0.000000}, {0.000000, 0.000000, 1.000000}},
{{0.000000, 1.000000, 0.000000}, {-1.000000, 0.000000, 0.000000}, {0.000000, 0.000000, 1.000000}},
{{-1.000000, 0.000000, 0.000000}, {0.000000, -1.000000, 0.000000}, {0.000000, 0.000000, 1.000000}},
{{0.000000, -1.000000, 0.000000}, {1.000000, 0.000000, 0.000000}, {0.000000, 0.000000, 1.000000}},
{{0.000000, 0.000000, -1.000000}, {0.000000, 1.000000, 0.000000}, {1.000000, 0.000000, 0.000000}},
{{0.000000, 1.000000, 0.000000}, {0.000000, 0.000000, 1.000000}, {1.000000, 0.000000, 0.000000}},
{{0.000000, 0.000000, 1.000000}, {0.000000, -1.000000, 0.000000}, {1.000000, 0.000000, 0.000000}},
{{0.000000, -1.000000, 0.000000}, {0.000000, 0.000000, -1.000000}, {1.000000, 0.000000, 0.000000}},
{{-1.000000, 0.000000, 0.000000}, {0.000000, 1.000000, 0.000000}, {0.000000, 0.000000, -1.000000}},
{{0.000000, 1.000000, 0.000000}, {1.000000, 0.000000, 0.000000}, {0.000000, 0.000000, -1.000000}},
{{1.000000, 0.000000, 0.000000}, {0.000000, -1.000000, 0.000000}, {0.000000, 0.000000, -1.000000}},
{{0.000000, -1.000000, 0.000000}, {-1.000000, 0.000000, 0.000000}, {0.000000, 0.000000, -1.000000}},
{{0.000000, 0.000000, 1.000000}, {0.000000, 1.000000, 0.000000}, {-1.000000, 0.000000, 0.000000}},
{{0.000000, 1.000000, 0.000000}, {0.000000, 0.000000, -1.000000}, {-1.000000, 0.000000, 0.000000}},
{{0.000000, 0.000000, -1.000000}, {0.000000, -1.000000, 0.000000}, {-1.000000, 0.000000, 0.000000}},
{{0.000000, -1.000000, 0.000000}, {0.000000, 0.000000, 1.000000}, {-1.000000, 0.000000, 0.000000}},
{{1.000000, 0.000000, 0.000000}, {0.000000, 0.000000, 1.000000}, {0.000000, -1.000000, 0.000000}},
{{0.000000, 0.000000, 1.000000}, {-1.000000, 0.000000, 0.000000}, {0.000000, -1.000000, 0.000000}},
{{-1.000000, 0.000000, 0.000000}, {0.000000, 0.000000, -1.000000}, {0.000000, -1.000000, 0.000000}},
{{0.000000, 0.000000, -1.000000}, {1.000000, 0.000000, 0.000000}, {0.000000, -1.000000, 0.000000}},
{{1.000000, 0.000000, 0.000000}, {0.000000, 0.000000, -1.000000}, {0.000000, 1.000000, 0.000000}},
{{0.000000, 0.000000, -1.000000}, {-1.000000, 0.000000, 0.000000}, {0.000000, 1.000000, 0.000000}},
{{-1.000000, 0.000000, 0.000000}, {0.000000, 0.000000, 1.000000}, {0.000000, 1.000000, 0.000000}},
{{0.000000, 0.000000, 1.000000}, {1.000000, 0.000000, 0.000000}, {0.000000, 1.000000, 0.000000}},
};

431
shaders/shaderlib.sh Normal file
View File

@@ -0,0 +1,431 @@
/*
* Copyright 2011-2022 Branimir Karadzic. All rights reserved.
* License: https://github.com/bkaradzic/bgfx/blob/master/LICENSE
*/
#ifndef __SHADERLIB_SH__
#define __SHADERLIB_SH__
vec4 encodeRE8(float _r)
{
float exponent = ceil(log2(_r) );
return vec4(_r / exp2(exponent)
, 0.0
, 0.0
, (exponent + 128.0) / 255.0
);
}
float decodeRE8(vec4 _re8)
{
float exponent = _re8.w * 255.0 - 128.0;
return _re8.x * exp2(exponent);
}
vec4 encodeRGBE8(vec3 _rgb)
{
vec4 rgbe8;
float maxComponent = max(max(_rgb.x, _rgb.y), _rgb.z);
float exponent = ceil(log2(maxComponent) );
rgbe8.xyz = _rgb / exp2(exponent);
rgbe8.w = (exponent + 128.0) / 255.0;
return rgbe8;
}
vec3 decodeRGBE8(vec4 _rgbe8)
{
float exponent = _rgbe8.w * 255.0 - 128.0;
vec3 rgb = _rgbe8.xyz * exp2(exponent);
return rgb;
}
vec3 encodeNormalUint(vec3 _normal)
{
return _normal * 0.5 + 0.5;
}
vec3 decodeNormalUint(vec3 _encodedNormal)
{
return _encodedNormal * 2.0 - 1.0;
}
vec2 encodeNormalSphereMap(vec3 _normal)
{
return normalize(_normal.xy) * sqrt(_normal.z * 0.5 + 0.5);
}
vec3 decodeNormalSphereMap(vec2 _encodedNormal)
{
float zz = dot(_encodedNormal, _encodedNormal) * 2.0 - 1.0;
return vec3(normalize(_encodedNormal.xy) * sqrt(1.0 - zz*zz), zz);
}
vec2 octahedronWrap(vec2 _val)
{
// Reference(s):
// - Octahedron normal vector encoding
// https://web.archive.org/web/20191027010600/https://knarkowicz.wordpress.com/2014/04/16/octahedron-normal-vector-encoding/comment-page-1/
return (1.0 - abs(_val.yx) )
* mix(vec2_splat(-1.0), vec2_splat(1.0), vec2(greaterThanEqual(_val.xy, vec2_splat(0.0) ) ) );
}
vec2 encodeNormalOctahedron(vec3 _normal)
{
_normal /= abs(_normal.x) + abs(_normal.y) + abs(_normal.z);
_normal.xy = _normal.z >= 0.0 ? _normal.xy : octahedronWrap(_normal.xy);
_normal.xy = _normal.xy * 0.5 + 0.5;
return _normal.xy;
}
vec3 decodeNormalOctahedron(vec2 _encodedNormal)
{
_encodedNormal = _encodedNormal * 2.0 - 1.0;
vec3 normal;
normal.z = 1.0 - abs(_encodedNormal.x) - abs(_encodedNormal.y);
normal.xy = normal.z >= 0.0 ? _encodedNormal.xy : octahedronWrap(_encodedNormal.xy);
return normalize(normal);
}
vec3 convertRGB2XYZ(vec3 _rgb)
{
// Reference(s):
// - RGB/XYZ Matrices
// https://web.archive.org/web/20191027010220/http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
vec3 xyz;
xyz.x = dot(vec3(0.4124564, 0.3575761, 0.1804375), _rgb);
xyz.y = dot(vec3(0.2126729, 0.7151522, 0.0721750), _rgb);
xyz.z = dot(vec3(0.0193339, 0.1191920, 0.9503041), _rgb);
return xyz;
}
vec3 convertXYZ2RGB(vec3 _xyz)
{
vec3 rgb;
rgb.x = dot(vec3( 3.2404542, -1.5371385, -0.4985314), _xyz);
rgb.y = dot(vec3(-0.9692660, 1.8760108, 0.0415560), _xyz);
rgb.z = dot(vec3( 0.0556434, -0.2040259, 1.0572252), _xyz);
return rgb;
}
vec3 convertXYZ2Yxy(vec3 _xyz)
{
// Reference(s):
// - XYZ to xyY
// https://web.archive.org/web/20191027010144/http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_xyY.html
float inv = 1.0/dot(_xyz, vec3(1.0, 1.0, 1.0) );
return vec3(_xyz.y, _xyz.x*inv, _xyz.y*inv);
}
vec3 convertYxy2XYZ(vec3 _Yxy)
{
// Reference(s):
// - xyY to XYZ
// https://web.archive.org/web/20191027010036/http://www.brucelindbloom.com/index.html?Eqn_xyY_to_XYZ.html
vec3 xyz;
xyz.x = _Yxy.x*_Yxy.y/_Yxy.z;
xyz.y = _Yxy.x;
xyz.z = _Yxy.x*(1.0 - _Yxy.y - _Yxy.z)/_Yxy.z;
return xyz;
}
vec3 convertRGB2Yxy(vec3 _rgb)
{
return convertXYZ2Yxy(convertRGB2XYZ(_rgb) );
}
vec3 convertYxy2RGB(vec3 _Yxy)
{
return convertXYZ2RGB(convertYxy2XYZ(_Yxy) );
}
vec3 convertRGB2Yuv(vec3 _rgb)
{
vec3 yuv;
yuv.x = dot(_rgb, vec3(0.299, 0.587, 0.114) );
yuv.y = (_rgb.x - yuv.x)*0.713 + 0.5;
yuv.z = (_rgb.z - yuv.x)*0.564 + 0.5;
return yuv;
}
vec3 convertYuv2RGB(vec3 _yuv)
{
vec3 rgb;
rgb.x = _yuv.x + 1.403*(_yuv.y-0.5);
rgb.y = _yuv.x - 0.344*(_yuv.y-0.5) - 0.714*(_yuv.z-0.5);
rgb.z = _yuv.x + 1.773*(_yuv.z-0.5);
return rgb;
}
vec3 convertRGB2YIQ(vec3 _rgb)
{
vec3 yiq;
yiq.x = dot(vec3(0.299, 0.587, 0.114 ), _rgb);
yiq.y = dot(vec3(0.595716, -0.274453, -0.321263), _rgb);
yiq.z = dot(vec3(0.211456, -0.522591, 0.311135), _rgb);
return yiq;
}
vec3 convertYIQ2RGB(vec3 _yiq)
{
vec3 rgb;
rgb.x = dot(vec3(1.0, 0.9563, 0.6210), _yiq);
rgb.y = dot(vec3(1.0, -0.2721, -0.6474), _yiq);
rgb.z = dot(vec3(1.0, -1.1070, 1.7046), _yiq);
return rgb;
}
vec3 toLinear(vec3 _rgb)
{
return pow(abs(_rgb), vec3_splat(2.2) );
}
vec4 toLinear(vec4 _rgba)
{
return vec4(toLinear(_rgba.xyz), _rgba.w);
}
vec3 toLinearAccurate(vec3 _rgb)
{
vec3 lo = _rgb / 12.92;
vec3 hi = pow( (_rgb + 0.055) / 1.055, vec3_splat(2.4) );
vec3 rgb = mix(hi, lo, vec3(lessThanEqual(_rgb, vec3_splat(0.04045) ) ) );
return rgb;
}
vec4 toLinearAccurate(vec4 _rgba)
{
return vec4(toLinearAccurate(_rgba.xyz), _rgba.w);
}
float toGamma(float _r)
{
return pow(abs(_r), 1.0/2.2);
}
vec3 toGamma(vec3 _rgb)
{
return pow(abs(_rgb), vec3_splat(1.0/2.2) );
}
vec4 toGamma(vec4 _rgba)
{
return vec4(toGamma(_rgba.xyz), _rgba.w);
}
vec3 toGammaAccurate(vec3 _rgb)
{
vec3 lo = _rgb * 12.92;
vec3 hi = pow(abs(_rgb), vec3_splat(1.0/2.4) ) * 1.055 - 0.055;
vec3 rgb = mix(hi, lo, vec3(lessThanEqual(_rgb, vec3_splat(0.0031308) ) ) );
return rgb;
}
vec4 toGammaAccurate(vec4 _rgba)
{
return vec4(toGammaAccurate(_rgba.xyz), _rgba.w);
}
vec3 toReinhard(vec3 _rgb)
{
return toGamma(_rgb/(_rgb+vec3_splat(1.0) ) );
}
vec4 toReinhard(vec4 _rgba)
{
return vec4(toReinhard(_rgba.xyz), _rgba.w);
}
vec3 toFilmic(vec3 _rgb)
{
_rgb = max(vec3_splat(0.0), _rgb - 0.004);
_rgb = (_rgb*(6.2*_rgb + 0.5) ) / (_rgb*(6.2*_rgb + 1.7) + 0.06);
return _rgb;
}
vec4 toFilmic(vec4 _rgba)
{
return vec4(toFilmic(_rgba.xyz), _rgba.w);
}
vec3 toAcesFilmic(vec3 _rgb)
{
// Reference(s):
// - ACES Filmic Tone Mapping Curve
// https://web.archive.org/web/20191027010704/https://knarkowicz.wordpress.com/2016/01/06/aces-filmic-tone-mapping-curve/
float aa = 2.51f;
float bb = 0.03f;
float cc = 2.43f;
float dd = 0.59f;
float ee = 0.14f;
return saturate( (_rgb*(aa*_rgb + bb) )/(_rgb*(cc*_rgb + dd) + ee) );
}
vec4 toAcesFilmic(vec4 _rgba)
{
return vec4(toAcesFilmic(_rgba.xyz), _rgba.w);
}
vec3 luma(vec3 _rgb)
{
float yy = dot(vec3(0.2126729, 0.7151522, 0.0721750), _rgb);
return vec3_splat(yy);
}
vec4 luma(vec4 _rgba)
{
return vec4(luma(_rgba.xyz), _rgba.w);
}
vec3 conSatBri(vec3 _rgb, vec3 _csb)
{
vec3 rgb = _rgb * _csb.z;
rgb = mix(luma(rgb), rgb, _csb.y);
rgb = mix(vec3_splat(0.5), rgb, _csb.x);
return rgb;
}
vec4 conSatBri(vec4 _rgba, vec3 _csb)
{
return vec4(conSatBri(_rgba.xyz, _csb), _rgba.w);
}
vec3 posterize(vec3 _rgb, float _numColors)
{
return floor(_rgb*_numColors) / _numColors;
}
vec4 posterize(vec4 _rgba, float _numColors)
{
return vec4(posterize(_rgba.xyz, _numColors), _rgba.w);
}
vec3 sepia(vec3 _rgb)
{
vec3 color;
color.x = dot(_rgb, vec3(0.393, 0.769, 0.189) );
color.y = dot(_rgb, vec3(0.349, 0.686, 0.168) );
color.z = dot(_rgb, vec3(0.272, 0.534, 0.131) );
return color;
}
vec4 sepia(vec4 _rgba)
{
return vec4(sepia(_rgba.xyz), _rgba.w);
}
vec3 blendOverlay(vec3 _base, vec3 _blend)
{
vec3 lt = 2.0 * _base * _blend;
vec3 gte = 1.0 - 2.0 * (1.0 - _base) * (1.0 - _blend);
return mix(lt, gte, step(vec3_splat(0.5), _base) );
}
vec4 blendOverlay(vec4 _base, vec4 _blend)
{
return vec4(blendOverlay(_base.xyz, _blend.xyz), _base.w);
}
vec3 adjustHue(vec3 _rgb, float _hue)
{
vec3 yiq = convertRGB2YIQ(_rgb);
float angle = _hue + atan2(yiq.z, yiq.y);
float len = length(yiq.yz);
return convertYIQ2RGB(vec3(yiq.x, len*cos(angle), len*sin(angle) ) );
}
vec4 packFloatToRgba(float _value)
{
const vec4 shift = vec4(256 * 256 * 256, 256 * 256, 256, 1.0);
const vec4 mask = vec4(0, 1.0 / 256.0, 1.0 / 256.0, 1.0 / 256.0);
vec4 comp = fract(_value * shift);
comp -= comp.xxyz * mask;
return comp;
}
float unpackRgbaToFloat(vec4 _rgba)
{
const vec4 shift = vec4(1.0 / (256.0 * 256.0 * 256.0), 1.0 / (256.0 * 256.0), 1.0 / 256.0, 1.0);
return dot(_rgba, shift);
}
vec2 packHalfFloat(float _value)
{
const vec2 shift = vec2(256, 1.0);
const vec2 mask = vec2(0, 1.0 / 256.0);
vec2 comp = fract(_value * shift);
comp -= comp.xx * mask;
return comp;
}
float unpackHalfFloat(vec2 _rg)
{
const vec2 shift = vec2(1.0 / 256.0, 1.0);
return dot(_rg, shift);
}
float random(vec2 _uv)
{
return fract(sin(dot(_uv.xy, vec2(12.9898, 78.233) ) ) * 43758.5453);
}
vec3 fixCubeLookup(vec3 _v, float _lod, float _topLevelCubeSize)
{
// Reference(s):
// - Seamless cube-map filtering
// https://web.archive.org/web/20190411181934/http://the-witness.net/news/2012/02/seamless-cube-map-filtering/
float ax = abs(_v.x);
float ay = abs(_v.y);
float az = abs(_v.z);
float vmax = max(max(ax, ay), az);
float scale = 1.0 - exp2(_lod) / _topLevelCubeSize;
if (ax != vmax) { _v.x *= scale; }
if (ay != vmax) { _v.y *= scale; }
if (az != vmax) { _v.z *= scale; }
return _v;
}
vec2 texture2DBc5(sampler2D _sampler, vec2 _uv)
{
#if BGFX_SHADER_LANGUAGE_HLSL && BGFX_SHADER_LANGUAGE_HLSL <= 300
return texture2D(_sampler, _uv).yx;
#else
return texture2D(_sampler, _uv).xy;
#endif
}
mat3 cofactor(mat4 _m)
{
// Reference:
// Cofactor of matrix. Use to transform normals. The code assumes the last column of _m is [0,0,0,1].
// https://www.shadertoy.com/view/3s33zj
// https://github.com/graphitemaster/normals_revisited
return mat3(
_m[1][1]*_m[2][2]-_m[1][2]*_m[2][1],
_m[1][2]*_m[2][0]-_m[1][0]*_m[2][2],
_m[1][0]*_m[2][1]-_m[1][1]*_m[2][0],
_m[0][2]*_m[2][1]-_m[0][1]*_m[2][2],
_m[0][0]*_m[2][2]-_m[0][2]*_m[2][0],
_m[0][1]*_m[2][0]-_m[0][0]*_m[2][1],
_m[0][1]*_m[1][2]-_m[0][2]*_m[1][1],
_m[0][2]*_m[1][0]-_m[0][0]*_m[1][2],
_m[0][0]*_m[1][1]-_m[0][1]*_m[1][0]
);
}
float toClipSpaceDepth(float _depthTextureZ)
{
#if BGFX_SHADER_LANGUAGE_GLSL
return _depthTextureZ * 2.0 - 1.0;
#else
return _depthTextureZ;
#endif // BGFX_SHADER_LANGUAGE_GLSL
}
vec3 clipToWorld(mat4 _invViewProj, vec3 _clipPos)
{
vec4 wpos = mul(_invViewProj, vec4(_clipPos, 1.0) );
return wpos.xyz / wpos.w;
}
#endif // __SHADERLIB_SH__

6
shaders/varying_cubes.sc Normal file
View File

@@ -0,0 +1,6 @@
vec2 tex_coord : TEXCOORD0 = vec2(0.0, 0.0);
vec3 world_pos : TEXCOORD1 = vec3(0.0, 0.0, 0.0);
vec4 i_data0 : TEXCOORD7;
vec4 i_data1 : TEXCOORD6;
vec4 i_data2 : TEXCOORD5;
vec4 i_data3 : TEXCOORD4;

1
shaders/varying_lines.sc Normal file
View File

@@ -0,0 +1 @@
vec3 a_position : POSITION;

732
shaders/verts.sh Normal file
View File

@@ -0,0 +1,732 @@
const vec3 verts[729] =
{
{-0.500000, -0.500000, -0.500000},
{-0.375000, -0.500000, -0.500000},
{-0.250000, -0.500000, -0.500000},
{-0.125000, -0.500000, -0.500000},
{0.000000, -0.500000, -0.500000},
{0.125000, -0.500000, -0.500000},
{0.250000, -0.500000, -0.500000},
{0.375000, -0.500000, -0.500000},
{0.500000, -0.500000, -0.500000},
{-0.500000, -0.375000, -0.500000},
{-0.375000, -0.375000, -0.500000},
{-0.250000, -0.375000, -0.500000},
{-0.125000, -0.375000, -0.500000},
{0.000000, -0.375000, -0.500000},
{0.125000, -0.375000, -0.500000},
{0.250000, -0.375000, -0.500000},
{0.375000, -0.375000, -0.500000},
{0.500000, -0.375000, -0.500000},
{-0.500000, -0.250000, -0.500000},
{-0.375000, -0.250000, -0.500000},
{-0.250000, -0.250000, -0.500000},
{-0.125000, -0.250000, -0.500000},
{0.000000, -0.250000, -0.500000},
{0.125000, -0.250000, -0.500000},
{0.250000, -0.250000, -0.500000},
{0.375000, -0.250000, -0.500000},
{0.500000, -0.250000, -0.500000},
{-0.500000, -0.125000, -0.500000},
{-0.375000, -0.125000, -0.500000},
{-0.250000, -0.125000, -0.500000},
{-0.125000, -0.125000, -0.500000},
{0.000000, -0.125000, -0.500000},
{0.125000, -0.125000, -0.500000},
{0.250000, -0.125000, -0.500000},
{0.375000, -0.125000, -0.500000},
{0.500000, -0.125000, -0.500000},
{-0.500000, 0.000000, -0.500000},
{-0.375000, 0.000000, -0.500000},
{-0.250000, 0.000000, -0.500000},
{-0.125000, 0.000000, -0.500000},
{0.000000, 0.000000, -0.500000},
{0.125000, 0.000000, -0.500000},
{0.250000, 0.000000, -0.500000},
{0.375000, 0.000000, -0.500000},
{0.500000, 0.000000, -0.500000},
{-0.500000, 0.125000, -0.500000},
{-0.375000, 0.125000, -0.500000},
{-0.250000, 0.125000, -0.500000},
{-0.125000, 0.125000, -0.500000},
{0.000000, 0.125000, -0.500000},
{0.125000, 0.125000, -0.500000},
{0.250000, 0.125000, -0.500000},
{0.375000, 0.125000, -0.500000},
{0.500000, 0.125000, -0.500000},
{-0.500000, 0.250000, -0.500000},
{-0.375000, 0.250000, -0.500000},
{-0.250000, 0.250000, -0.500000},
{-0.125000, 0.250000, -0.500000},
{0.000000, 0.250000, -0.500000},
{0.125000, 0.250000, -0.500000},
{0.250000, 0.250000, -0.500000},
{0.375000, 0.250000, -0.500000},
{0.500000, 0.250000, -0.500000},
{-0.500000, 0.375000, -0.500000},
{-0.375000, 0.375000, -0.500000},
{-0.250000, 0.375000, -0.500000},
{-0.125000, 0.375000, -0.500000},
{0.000000, 0.375000, -0.500000},
{0.125000, 0.375000, -0.500000},
{0.250000, 0.375000, -0.500000},
{0.375000, 0.375000, -0.500000},
{0.500000, 0.375000, -0.500000},
{-0.500000, 0.500000, -0.500000},
{-0.375000, 0.500000, -0.500000},
{-0.250000, 0.500000, -0.500000},
{-0.125000, 0.500000, -0.500000},
{0.000000, 0.500000, -0.500000},
{0.125000, 0.500000, -0.500000},
{0.250000, 0.500000, -0.500000},
{0.375000, 0.500000, -0.500000},
{0.500000, 0.500000, -0.500000},
{-0.500000, -0.500000, -0.375000},
{-0.375000, -0.500000, -0.375000},
{-0.250000, -0.500000, -0.375000},
{-0.125000, -0.500000, -0.375000},
{0.000000, -0.500000, -0.375000},
{0.125000, -0.500000, -0.375000},
{0.250000, -0.500000, -0.375000},
{0.375000, -0.500000, -0.375000},
{0.500000, -0.500000, -0.375000},
{-0.500000, -0.375000, -0.375000},
{-0.375000, -0.375000, -0.375000},
{-0.250000, -0.375000, -0.375000},
{-0.125000, -0.375000, -0.375000},
{0.000000, -0.375000, -0.375000},
{0.125000, -0.375000, -0.375000},
{0.250000, -0.375000, -0.375000},
{0.375000, -0.375000, -0.375000},
{0.500000, -0.375000, -0.375000},
{-0.500000, -0.250000, -0.375000},
{-0.375000, -0.250000, -0.375000},
{-0.250000, -0.250000, -0.375000},
{-0.125000, -0.250000, -0.375000},
{0.000000, -0.250000, -0.375000},
{0.125000, -0.250000, -0.375000},
{0.250000, -0.250000, -0.375000},
{0.375000, -0.250000, -0.375000},
{0.500000, -0.250000, -0.375000},
{-0.500000, -0.125000, -0.375000},
{-0.375000, -0.125000, -0.375000},
{-0.250000, -0.125000, -0.375000},
{-0.125000, -0.125000, -0.375000},
{0.000000, -0.125000, -0.375000},
{0.125000, -0.125000, -0.375000},
{0.250000, -0.125000, -0.375000},
{0.375000, -0.125000, -0.375000},
{0.500000, -0.125000, -0.375000},
{-0.500000, 0.000000, -0.375000},
{-0.375000, 0.000000, -0.375000},
{-0.250000, 0.000000, -0.375000},
{-0.125000, 0.000000, -0.375000},
{0.000000, 0.000000, -0.375000},
{0.125000, 0.000000, -0.375000},
{0.250000, 0.000000, -0.375000},
{0.375000, 0.000000, -0.375000},
{0.500000, 0.000000, -0.375000},
{-0.500000, 0.125000, -0.375000},
{-0.375000, 0.125000, -0.375000},
{-0.250000, 0.125000, -0.375000},
{-0.125000, 0.125000, -0.375000},
{0.000000, 0.125000, -0.375000},
{0.125000, 0.125000, -0.375000},
{0.250000, 0.125000, -0.375000},
{0.375000, 0.125000, -0.375000},
{0.500000, 0.125000, -0.375000},
{-0.500000, 0.250000, -0.375000},
{-0.375000, 0.250000, -0.375000},
{-0.250000, 0.250000, -0.375000},
{-0.125000, 0.250000, -0.375000},
{0.000000, 0.250000, -0.375000},
{0.125000, 0.250000, -0.375000},
{0.250000, 0.250000, -0.375000},
{0.375000, 0.250000, -0.375000},
{0.500000, 0.250000, -0.375000},
{-0.500000, 0.375000, -0.375000},
{-0.375000, 0.375000, -0.375000},
{-0.250000, 0.375000, -0.375000},
{-0.125000, 0.375000, -0.375000},
{0.000000, 0.375000, -0.375000},
{0.125000, 0.375000, -0.375000},
{0.250000, 0.375000, -0.375000},
{0.375000, 0.375000, -0.375000},
{0.500000, 0.375000, -0.375000},
{-0.500000, 0.500000, -0.375000},
{-0.375000, 0.500000, -0.375000},
{-0.250000, 0.500000, -0.375000},
{-0.125000, 0.500000, -0.375000},
{0.000000, 0.500000, -0.375000},
{0.125000, 0.500000, -0.375000},
{0.250000, 0.500000, -0.375000},
{0.375000, 0.500000, -0.375000},
{0.500000, 0.500000, -0.375000},
{-0.500000, -0.500000, -0.250000},
{-0.375000, -0.500000, -0.250000},
{-0.250000, -0.500000, -0.250000},
{-0.125000, -0.500000, -0.250000},
{0.000000, -0.500000, -0.250000},
{0.125000, -0.500000, -0.250000},
{0.250000, -0.500000, -0.250000},
{0.375000, -0.500000, -0.250000},
{0.500000, -0.500000, -0.250000},
{-0.500000, -0.375000, -0.250000},
{-0.375000, -0.375000, -0.250000},
{-0.250000, -0.375000, -0.250000},
{-0.125000, -0.375000, -0.250000},
{0.000000, -0.375000, -0.250000},
{0.125000, -0.375000, -0.250000},
{0.250000, -0.375000, -0.250000},
{0.375000, -0.375000, -0.250000},
{0.500000, -0.375000, -0.250000},
{-0.500000, -0.250000, -0.250000},
{-0.375000, -0.250000, -0.250000},
{-0.250000, -0.250000, -0.250000},
{-0.125000, -0.250000, -0.250000},
{0.000000, -0.250000, -0.250000},
{0.125000, -0.250000, -0.250000},
{0.250000, -0.250000, -0.250000},
{0.375000, -0.250000, -0.250000},
{0.500000, -0.250000, -0.250000},
{-0.500000, -0.125000, -0.250000},
{-0.375000, -0.125000, -0.250000},
{-0.250000, -0.125000, -0.250000},
{-0.125000, -0.125000, -0.250000},
{0.000000, -0.125000, -0.250000},
{0.125000, -0.125000, -0.250000},
{0.250000, -0.125000, -0.250000},
{0.375000, -0.125000, -0.250000},
{0.500000, -0.125000, -0.250000},
{-0.500000, 0.000000, -0.250000},
{-0.375000, 0.000000, -0.250000},
{-0.250000, 0.000000, -0.250000},
{-0.125000, 0.000000, -0.250000},
{0.000000, 0.000000, -0.250000},
{0.125000, 0.000000, -0.250000},
{0.250000, 0.000000, -0.250000},
{0.375000, 0.000000, -0.250000},
{0.500000, 0.000000, -0.250000},
{-0.500000, 0.125000, -0.250000},
{-0.375000, 0.125000, -0.250000},
{-0.250000, 0.125000, -0.250000},
{-0.125000, 0.125000, -0.250000},
{0.000000, 0.125000, -0.250000},
{0.125000, 0.125000, -0.250000},
{0.250000, 0.125000, -0.250000},
{0.375000, 0.125000, -0.250000},
{0.500000, 0.125000, -0.250000},
{-0.500000, 0.250000, -0.250000},
{-0.375000, 0.250000, -0.250000},
{-0.250000, 0.250000, -0.250000},
{-0.125000, 0.250000, -0.250000},
{0.000000, 0.250000, -0.250000},
{0.125000, 0.250000, -0.250000},
{0.250000, 0.250000, -0.250000},
{0.375000, 0.250000, -0.250000},
{0.500000, 0.250000, -0.250000},
{-0.500000, 0.375000, -0.250000},
{-0.375000, 0.375000, -0.250000},
{-0.250000, 0.375000, -0.250000},
{-0.125000, 0.375000, -0.250000},
{0.000000, 0.375000, -0.250000},
{0.125000, 0.375000, -0.250000},
{0.250000, 0.375000, -0.250000},
{0.375000, 0.375000, -0.250000},
{0.500000, 0.375000, -0.250000},
{-0.500000, 0.500000, -0.250000},
{-0.375000, 0.500000, -0.250000},
{-0.250000, 0.500000, -0.250000},
{-0.125000, 0.500000, -0.250000},
{0.000000, 0.500000, -0.250000},
{0.125000, 0.500000, -0.250000},
{0.250000, 0.500000, -0.250000},
{0.375000, 0.500000, -0.250000},
{0.500000, 0.500000, -0.250000},
{-0.500000, -0.500000, -0.125000},
{-0.375000, -0.500000, -0.125000},
{-0.250000, -0.500000, -0.125000},
{-0.125000, -0.500000, -0.125000},
{0.000000, -0.500000, -0.125000},
{0.125000, -0.500000, -0.125000},
{0.250000, -0.500000, -0.125000},
{0.375000, -0.500000, -0.125000},
{0.500000, -0.500000, -0.125000},
{-0.500000, -0.375000, -0.125000},
{-0.375000, -0.375000, -0.125000},
{-0.250000, -0.375000, -0.125000},
{-0.125000, -0.375000, -0.125000},
{0.000000, -0.375000, -0.125000},
{0.125000, -0.375000, -0.125000},
{0.250000, -0.375000, -0.125000},
{0.375000, -0.375000, -0.125000},
{0.500000, -0.375000, -0.125000},
{-0.500000, -0.250000, -0.125000},
{-0.375000, -0.250000, -0.125000},
{-0.250000, -0.250000, -0.125000},
{-0.125000, -0.250000, -0.125000},
{0.000000, -0.250000, -0.125000},
{0.125000, -0.250000, -0.125000},
{0.250000, -0.250000, -0.125000},
{0.375000, -0.250000, -0.125000},
{0.500000, -0.250000, -0.125000},
{-0.500000, -0.125000, -0.125000},
{-0.375000, -0.125000, -0.125000},
{-0.250000, -0.125000, -0.125000},
{-0.125000, -0.125000, -0.125000},
{0.000000, -0.125000, -0.125000},
{0.125000, -0.125000, -0.125000},
{0.250000, -0.125000, -0.125000},
{0.375000, -0.125000, -0.125000},
{0.500000, -0.125000, -0.125000},
{-0.500000, 0.000000, -0.125000},
{-0.375000, 0.000000, -0.125000},
{-0.250000, 0.000000, -0.125000},
{-0.125000, 0.000000, -0.125000},
{0.000000, 0.000000, -0.125000},
{0.125000, 0.000000, -0.125000},
{0.250000, 0.000000, -0.125000},
{0.375000, 0.000000, -0.125000},
{0.500000, 0.000000, -0.125000},
{-0.500000, 0.125000, -0.125000},
{-0.375000, 0.125000, -0.125000},
{-0.250000, 0.125000, -0.125000},
{-0.125000, 0.125000, -0.125000},
{0.000000, 0.125000, -0.125000},
{0.125000, 0.125000, -0.125000},
{0.250000, 0.125000, -0.125000},
{0.375000, 0.125000, -0.125000},
{0.500000, 0.125000, -0.125000},
{-0.500000, 0.250000, -0.125000},
{-0.375000, 0.250000, -0.125000},
{-0.250000, 0.250000, -0.125000},
{-0.125000, 0.250000, -0.125000},
{0.000000, 0.250000, -0.125000},
{0.125000, 0.250000, -0.125000},
{0.250000, 0.250000, -0.125000},
{0.375000, 0.250000, -0.125000},
{0.500000, 0.250000, -0.125000},
{-0.500000, 0.375000, -0.125000},
{-0.375000, 0.375000, -0.125000},
{-0.250000, 0.375000, -0.125000},
{-0.125000, 0.375000, -0.125000},
{0.000000, 0.375000, -0.125000},
{0.125000, 0.375000, -0.125000},
{0.250000, 0.375000, -0.125000},
{0.375000, 0.375000, -0.125000},
{0.500000, 0.375000, -0.125000},
{-0.500000, 0.500000, -0.125000},
{-0.375000, 0.500000, -0.125000},
{-0.250000, 0.500000, -0.125000},
{-0.125000, 0.500000, -0.125000},
{0.000000, 0.500000, -0.125000},
{0.125000, 0.500000, -0.125000},
{0.250000, 0.500000, -0.125000},
{0.375000, 0.500000, -0.125000},
{0.500000, 0.500000, -0.125000},
{-0.500000, -0.500000, 0.000000},
{-0.375000, -0.500000, 0.000000},
{-0.250000, -0.500000, 0.000000},
{-0.125000, -0.500000, 0.000000},
{0.000000, -0.500000, 0.000000},
{0.125000, -0.500000, 0.000000},
{0.250000, -0.500000, 0.000000},
{0.375000, -0.500000, 0.000000},
{0.500000, -0.500000, 0.000000},
{-0.500000, -0.375000, 0.000000},
{-0.375000, -0.375000, 0.000000},
{-0.250000, -0.375000, 0.000000},
{-0.125000, -0.375000, 0.000000},
{0.000000, -0.375000, 0.000000},
{0.125000, -0.375000, 0.000000},
{0.250000, -0.375000, 0.000000},
{0.375000, -0.375000, 0.000000},
{0.500000, -0.375000, 0.000000},
{-0.500000, -0.250000, 0.000000},
{-0.375000, -0.250000, 0.000000},
{-0.250000, -0.250000, 0.000000},
{-0.125000, -0.250000, 0.000000},
{0.000000, -0.250000, 0.000000},
{0.125000, -0.250000, 0.000000},
{0.250000, -0.250000, 0.000000},
{0.375000, -0.250000, 0.000000},
{0.500000, -0.250000, 0.000000},
{-0.500000, -0.125000, 0.000000},
{-0.375000, -0.125000, 0.000000},
{-0.250000, -0.125000, 0.000000},
{-0.125000, -0.125000, 0.000000},
{0.000000, -0.125000, 0.000000},
{0.125000, -0.125000, 0.000000},
{0.250000, -0.125000, 0.000000},
{0.375000, -0.125000, 0.000000},
{0.500000, -0.125000, 0.000000},
{-0.500000, 0.000000, 0.000000},
{-0.375000, 0.000000, 0.000000},
{-0.250000, 0.000000, 0.000000},
{-0.125000, 0.000000, 0.000000},
{0.000000, 0.000000, 0.000000},
{0.125000, 0.000000, 0.000000},
{0.250000, 0.000000, 0.000000},
{0.375000, 0.000000, 0.000000},
{0.500000, 0.000000, 0.000000},
{-0.500000, 0.125000, 0.000000},
{-0.375000, 0.125000, 0.000000},
{-0.250000, 0.125000, 0.000000},
{-0.125000, 0.125000, 0.000000},
{0.000000, 0.125000, 0.000000},
{0.125000, 0.125000, 0.000000},
{0.250000, 0.125000, 0.000000},
{0.375000, 0.125000, 0.000000},
{0.500000, 0.125000, 0.000000},
{-0.500000, 0.250000, 0.000000},
{-0.375000, 0.250000, 0.000000},
{-0.250000, 0.250000, 0.000000},
{-0.125000, 0.250000, 0.000000},
{0.000000, 0.250000, 0.000000},
{0.125000, 0.250000, 0.000000},
{0.250000, 0.250000, 0.000000},
{0.375000, 0.250000, 0.000000},
{0.500000, 0.250000, 0.000000},
{-0.500000, 0.375000, 0.000000},
{-0.375000, 0.375000, 0.000000},
{-0.250000, 0.375000, 0.000000},
{-0.125000, 0.375000, 0.000000},
{0.000000, 0.375000, 0.000000},
{0.125000, 0.375000, 0.000000},
{0.250000, 0.375000, 0.000000},
{0.375000, 0.375000, 0.000000},
{0.500000, 0.375000, 0.000000},
{-0.500000, 0.500000, 0.000000},
{-0.375000, 0.500000, 0.000000},
{-0.250000, 0.500000, 0.000000},
{-0.125000, 0.500000, 0.000000},
{0.000000, 0.500000, 0.000000},
{0.125000, 0.500000, 0.000000},
{0.250000, 0.500000, 0.000000},
{0.375000, 0.500000, 0.000000},
{0.500000, 0.500000, 0.000000},
{-0.500000, -0.500000, 0.125000},
{-0.375000, -0.500000, 0.125000},
{-0.250000, -0.500000, 0.125000},
{-0.125000, -0.500000, 0.125000},
{0.000000, -0.500000, 0.125000},
{0.125000, -0.500000, 0.125000},
{0.250000, -0.500000, 0.125000},
{0.375000, -0.500000, 0.125000},
{0.500000, -0.500000, 0.125000},
{-0.500000, -0.375000, 0.125000},
{-0.375000, -0.375000, 0.125000},
{-0.250000, -0.375000, 0.125000},
{-0.125000, -0.375000, 0.125000},
{0.000000, -0.375000, 0.125000},
{0.125000, -0.375000, 0.125000},
{0.250000, -0.375000, 0.125000},
{0.375000, -0.375000, 0.125000},
{0.500000, -0.375000, 0.125000},
{-0.500000, -0.250000, 0.125000},
{-0.375000, -0.250000, 0.125000},
{-0.250000, -0.250000, 0.125000},
{-0.125000, -0.250000, 0.125000},
{0.000000, -0.250000, 0.125000},
{0.125000, -0.250000, 0.125000},
{0.250000, -0.250000, 0.125000},
{0.375000, -0.250000, 0.125000},
{0.500000, -0.250000, 0.125000},
{-0.500000, -0.125000, 0.125000},
{-0.375000, -0.125000, 0.125000},
{-0.250000, -0.125000, 0.125000},
{-0.125000, -0.125000, 0.125000},
{0.000000, -0.125000, 0.125000},
{0.125000, -0.125000, 0.125000},
{0.250000, -0.125000, 0.125000},
{0.375000, -0.125000, 0.125000},
{0.500000, -0.125000, 0.125000},
{-0.500000, 0.000000, 0.125000},
{-0.375000, 0.000000, 0.125000},
{-0.250000, 0.000000, 0.125000},
{-0.125000, 0.000000, 0.125000},
{0.000000, 0.000000, 0.125000},
{0.125000, 0.000000, 0.125000},
{0.250000, 0.000000, 0.125000},
{0.375000, 0.000000, 0.125000},
{0.500000, 0.000000, 0.125000},
{-0.500000, 0.125000, 0.125000},
{-0.375000, 0.125000, 0.125000},
{-0.250000, 0.125000, 0.125000},
{-0.125000, 0.125000, 0.125000},
{0.000000, 0.125000, 0.125000},
{0.125000, 0.125000, 0.125000},
{0.250000, 0.125000, 0.125000},
{0.375000, 0.125000, 0.125000},
{0.500000, 0.125000, 0.125000},
{-0.500000, 0.250000, 0.125000},
{-0.375000, 0.250000, 0.125000},
{-0.250000, 0.250000, 0.125000},
{-0.125000, 0.250000, 0.125000},
{0.000000, 0.250000, 0.125000},
{0.125000, 0.250000, 0.125000},
{0.250000, 0.250000, 0.125000},
{0.375000, 0.250000, 0.125000},
{0.500000, 0.250000, 0.125000},
{-0.500000, 0.375000, 0.125000},
{-0.375000, 0.375000, 0.125000},
{-0.250000, 0.375000, 0.125000},
{-0.125000, 0.375000, 0.125000},
{0.000000, 0.375000, 0.125000},
{0.125000, 0.375000, 0.125000},
{0.250000, 0.375000, 0.125000},
{0.375000, 0.375000, 0.125000},
{0.500000, 0.375000, 0.125000},
{-0.500000, 0.500000, 0.125000},
{-0.375000, 0.500000, 0.125000},
{-0.250000, 0.500000, 0.125000},
{-0.125000, 0.500000, 0.125000},
{0.000000, 0.500000, 0.125000},
{0.125000, 0.500000, 0.125000},
{0.250000, 0.500000, 0.125000},
{0.375000, 0.500000, 0.125000},
{0.500000, 0.500000, 0.125000},
{-0.500000, -0.500000, 0.250000},
{-0.375000, -0.500000, 0.250000},
{-0.250000, -0.500000, 0.250000},
{-0.125000, -0.500000, 0.250000},
{0.000000, -0.500000, 0.250000},
{0.125000, -0.500000, 0.250000},
{0.250000, -0.500000, 0.250000},
{0.375000, -0.500000, 0.250000},
{0.500000, -0.500000, 0.250000},
{-0.500000, -0.375000, 0.250000},
{-0.375000, -0.375000, 0.250000},
{-0.250000, -0.375000, 0.250000},
{-0.125000, -0.375000, 0.250000},
{0.000000, -0.375000, 0.250000},
{0.125000, -0.375000, 0.250000},
{0.250000, -0.375000, 0.250000},
{0.375000, -0.375000, 0.250000},
{0.500000, -0.375000, 0.250000},
{-0.500000, -0.250000, 0.250000},
{-0.375000, -0.250000, 0.250000},
{-0.250000, -0.250000, 0.250000},
{-0.125000, -0.250000, 0.250000},
{0.000000, -0.250000, 0.250000},
{0.125000, -0.250000, 0.250000},
{0.250000, -0.250000, 0.250000},
{0.375000, -0.250000, 0.250000},
{0.500000, -0.250000, 0.250000},
{-0.500000, -0.125000, 0.250000},
{-0.375000, -0.125000, 0.250000},
{-0.250000, -0.125000, 0.250000},
{-0.125000, -0.125000, 0.250000},
{0.000000, -0.125000, 0.250000},
{0.125000, -0.125000, 0.250000},
{0.250000, -0.125000, 0.250000},
{0.375000, -0.125000, 0.250000},
{0.500000, -0.125000, 0.250000},
{-0.500000, 0.000000, 0.250000},
{-0.375000, 0.000000, 0.250000},
{-0.250000, 0.000000, 0.250000},
{-0.125000, 0.000000, 0.250000},
{0.000000, 0.000000, 0.250000},
{0.125000, 0.000000, 0.250000},
{0.250000, 0.000000, 0.250000},
{0.375000, 0.000000, 0.250000},
{0.500000, 0.000000, 0.250000},
{-0.500000, 0.125000, 0.250000},
{-0.375000, 0.125000, 0.250000},
{-0.250000, 0.125000, 0.250000},
{-0.125000, 0.125000, 0.250000},
{0.000000, 0.125000, 0.250000},
{0.125000, 0.125000, 0.250000},
{0.250000, 0.125000, 0.250000},
{0.375000, 0.125000, 0.250000},
{0.500000, 0.125000, 0.250000},
{-0.500000, 0.250000, 0.250000},
{-0.375000, 0.250000, 0.250000},
{-0.250000, 0.250000, 0.250000},
{-0.125000, 0.250000, 0.250000},
{0.000000, 0.250000, 0.250000},
{0.125000, 0.250000, 0.250000},
{0.250000, 0.250000, 0.250000},
{0.375000, 0.250000, 0.250000},
{0.500000, 0.250000, 0.250000},
{-0.500000, 0.375000, 0.250000},
{-0.375000, 0.375000, 0.250000},
{-0.250000, 0.375000, 0.250000},
{-0.125000, 0.375000, 0.250000},
{0.000000, 0.375000, 0.250000},
{0.125000, 0.375000, 0.250000},
{0.250000, 0.375000, 0.250000},
{0.375000, 0.375000, 0.250000},
{0.500000, 0.375000, 0.250000},
{-0.500000, 0.500000, 0.250000},
{-0.375000, 0.500000, 0.250000},
{-0.250000, 0.500000, 0.250000},
{-0.125000, 0.500000, 0.250000},
{0.000000, 0.500000, 0.250000},
{0.125000, 0.500000, 0.250000},
{0.250000, 0.500000, 0.250000},
{0.375000, 0.500000, 0.250000},
{0.500000, 0.500000, 0.250000},
{-0.500000, -0.500000, 0.375000},
{-0.375000, -0.500000, 0.375000},
{-0.250000, -0.500000, 0.375000},
{-0.125000, -0.500000, 0.375000},
{0.000000, -0.500000, 0.375000},
{0.125000, -0.500000, 0.375000},
{0.250000, -0.500000, 0.375000},
{0.375000, -0.500000, 0.375000},
{0.500000, -0.500000, 0.375000},
{-0.500000, -0.375000, 0.375000},
{-0.375000, -0.375000, 0.375000},
{-0.250000, -0.375000, 0.375000},
{-0.125000, -0.375000, 0.375000},
{0.000000, -0.375000, 0.375000},
{0.125000, -0.375000, 0.375000},
{0.250000, -0.375000, 0.375000},
{0.375000, -0.375000, 0.375000},
{0.500000, -0.375000, 0.375000},
{-0.500000, -0.250000, 0.375000},
{-0.375000, -0.250000, 0.375000},
{-0.250000, -0.250000, 0.375000},
{-0.125000, -0.250000, 0.375000},
{0.000000, -0.250000, 0.375000},
{0.125000, -0.250000, 0.375000},
{0.250000, -0.250000, 0.375000},
{0.375000, -0.250000, 0.375000},
{0.500000, -0.250000, 0.375000},
{-0.500000, -0.125000, 0.375000},
{-0.375000, -0.125000, 0.375000},
{-0.250000, -0.125000, 0.375000},
{-0.125000, -0.125000, 0.375000},
{0.000000, -0.125000, 0.375000},
{0.125000, -0.125000, 0.375000},
{0.250000, -0.125000, 0.375000},
{0.375000, -0.125000, 0.375000},
{0.500000, -0.125000, 0.375000},
{-0.500000, 0.000000, 0.375000},
{-0.375000, 0.000000, 0.375000},
{-0.250000, 0.000000, 0.375000},
{-0.125000, 0.000000, 0.375000},
{0.000000, 0.000000, 0.375000},
{0.125000, 0.000000, 0.375000},
{0.250000, 0.000000, 0.375000},
{0.375000, 0.000000, 0.375000},
{0.500000, 0.000000, 0.375000},
{-0.500000, 0.125000, 0.375000},
{-0.375000, 0.125000, 0.375000},
{-0.250000, 0.125000, 0.375000},
{-0.125000, 0.125000, 0.375000},
{0.000000, 0.125000, 0.375000},
{0.125000, 0.125000, 0.375000},
{0.250000, 0.125000, 0.375000},
{0.375000, 0.125000, 0.375000},
{0.500000, 0.125000, 0.375000},
{-0.500000, 0.250000, 0.375000},
{-0.375000, 0.250000, 0.375000},
{-0.250000, 0.250000, 0.375000},
{-0.125000, 0.250000, 0.375000},
{0.000000, 0.250000, 0.375000},
{0.125000, 0.250000, 0.375000},
{0.250000, 0.250000, 0.375000},
{0.375000, 0.250000, 0.375000},
{0.500000, 0.250000, 0.375000},
{-0.500000, 0.375000, 0.375000},
{-0.375000, 0.375000, 0.375000},
{-0.250000, 0.375000, 0.375000},
{-0.125000, 0.375000, 0.375000},
{0.000000, 0.375000, 0.375000},
{0.125000, 0.375000, 0.375000},
{0.250000, 0.375000, 0.375000},
{0.375000, 0.375000, 0.375000},
{0.500000, 0.375000, 0.375000},
{-0.500000, 0.500000, 0.375000},
{-0.375000, 0.500000, 0.375000},
{-0.250000, 0.500000, 0.375000},
{-0.125000, 0.500000, 0.375000},
{0.000000, 0.500000, 0.375000},
{0.125000, 0.500000, 0.375000},
{0.250000, 0.500000, 0.375000},
{0.375000, 0.500000, 0.375000},
{0.500000, 0.500000, 0.375000},
{-0.500000, -0.500000, 0.500000},
{-0.375000, -0.500000, 0.500000},
{-0.250000, -0.500000, 0.500000},
{-0.125000, -0.500000, 0.500000},
{0.000000, -0.500000, 0.500000},
{0.125000, -0.500000, 0.500000},
{0.250000, -0.500000, 0.500000},
{0.375000, -0.500000, 0.500000},
{0.500000, -0.500000, 0.500000},
{-0.500000, -0.375000, 0.500000},
{-0.375000, -0.375000, 0.500000},
{-0.250000, -0.375000, 0.500000},
{-0.125000, -0.375000, 0.500000},
{0.000000, -0.375000, 0.500000},
{0.125000, -0.375000, 0.500000},
{0.250000, -0.375000, 0.500000},
{0.375000, -0.375000, 0.500000},
{0.500000, -0.375000, 0.500000},
{-0.500000, -0.250000, 0.500000},
{-0.375000, -0.250000, 0.500000},
{-0.250000, -0.250000, 0.500000},
{-0.125000, -0.250000, 0.500000},
{0.000000, -0.250000, 0.500000},
{0.125000, -0.250000, 0.500000},
{0.250000, -0.250000, 0.500000},
{0.375000, -0.250000, 0.500000},
{0.500000, -0.250000, 0.500000},
{-0.500000, -0.125000, 0.500000},
{-0.375000, -0.125000, 0.500000},
{-0.250000, -0.125000, 0.500000},
{-0.125000, -0.125000, 0.500000},
{0.000000, -0.125000, 0.500000},
{0.125000, -0.125000, 0.500000},
{0.250000, -0.125000, 0.500000},
{0.375000, -0.125000, 0.500000},
{0.500000, -0.125000, 0.500000},
{-0.500000, 0.000000, 0.500000},
{-0.375000, 0.000000, 0.500000},
{-0.250000, 0.000000, 0.500000},
{-0.125000, 0.000000, 0.500000},
{0.000000, 0.000000, 0.500000},
{0.125000, 0.000000, 0.500000},
{0.250000, 0.000000, 0.500000},
{0.375000, 0.000000, 0.500000},
{0.500000, 0.000000, 0.500000},
{-0.500000, 0.125000, 0.500000},
{-0.375000, 0.125000, 0.500000},
{-0.250000, 0.125000, 0.500000},
{-0.125000, 0.125000, 0.500000},
{0.000000, 0.125000, 0.500000},
{0.125000, 0.125000, 0.500000},
{0.250000, 0.125000, 0.500000},
{0.375000, 0.125000, 0.500000},
{0.500000, 0.125000, 0.500000},
{-0.500000, 0.250000, 0.500000},
{-0.375000, 0.250000, 0.500000},
{-0.250000, 0.250000, 0.500000},
{-0.125000, 0.250000, 0.500000},
{0.000000, 0.250000, 0.500000},
{0.125000, 0.250000, 0.500000},
{0.250000, 0.250000, 0.500000},
{0.375000, 0.250000, 0.500000},
{0.500000, 0.250000, 0.500000},
{-0.500000, 0.375000, 0.500000},
{-0.375000, 0.375000, 0.500000},
{-0.250000, 0.375000, 0.500000},
{-0.125000, 0.375000, 0.500000},
{0.000000, 0.375000, 0.500000},
{0.125000, 0.375000, 0.500000},
{0.250000, 0.375000, 0.500000},
{0.375000, 0.375000, 0.500000},
{0.500000, 0.375000, 0.500000},
{-0.500000, 0.500000, 0.500000},
{-0.375000, 0.500000, 0.500000},
{-0.250000, 0.500000, 0.500000},
{-0.125000, 0.500000, 0.500000},
{0.000000, 0.500000, 0.500000},
{0.125000, 0.500000, 0.500000},
{0.250000, 0.500000, 0.500000},
{0.375000, 0.500000, 0.500000},
{0.500000, 0.500000, 0.500000},
};

97
shaders/vs_cubes.sc Normal file
View File

@@ -0,0 +1,97 @@
$input i_data0, i_data1, i_data2, i_data3
$output tex_coord, world_pos
#include "verts.sh"
#include "bgfx_shader.sh"
#include "shaderlib.sh"
// ATTENTION must match config.h
// VertexID Layout for texturing (not used)
// Bits: 19 | 3 | 10
// Data: texture index | texture corner | vertex index
const uint vertex_index_bits = 10;
const uint vertex_index_mask = (1 << vertex_index_bits) - 1;
const uint texture_corner_bits = 3;
const uint texture_corner_mask = (1 << texture_corner_bits) - 1;
const uint texture_index_bits = 19;
const uint texture_index_mask = (1 << texture_index_bits) - 1;
const uint TA_WIDTH = 2; // patches in atlas
const uint TA_HEIGHT = 2; // patches in atlas
const uint TA_MAIN_PATCH_SIZE = 8; // textures per patch
const uint TA_TEXTURE_SIZE = 4; // texels per texture
const uint TA_TEXTURES_PER_ROW = TA_WIDTH * TA_MAIN_PATCH_SIZE;
const uint TA_TEXTURES_PER_COL = TA_HEIGHT * TA_MAIN_PATCH_SIZE;
const float texel_stride_x = 1.0f / (TA_WIDTH * TA_MAIN_PATCH_SIZE * TA_TEXTURE_SIZE);
const float texel_stride_y = 1.0f / (TA_HEIGHT * TA_MAIN_PATCH_SIZE * TA_TEXTURE_SIZE);
const float texture_stride_x = 1.0f / TA_TEXTURES_PER_ROW;
const float texture_stride_y = 1.0f / TA_TEXTURES_PER_ROW;
const vec2 corner_offsets[8] =
{
{ 0.000000, 0.000000}, // TopLeft
{ 0.000000, 2*texel_stride_y}, // CenterLeft
{ 0.000000, 4*texel_stride_y}, // BotLeft
{ 2*texel_stride_x, 4*texel_stride_y}, // CenterBot
{ 4*texel_stride_x, 4*texel_stride_y}, // BotRight
{ 4*texel_stride_x, 2*texel_stride_y}, // CenterRight
{ 4*texel_stride_x, 0.000000}, // TopRight
{ 2*texel_stride_x, 0.000000}, // CenterTop
};
// Texturing rework V2
const uint IA_WIDTH = 2*8; // id's per row
const uint IA_HEIGHT = 2*8; // id's per column
const float ID_STRIDE_X = 1.0f / IA_WIDTH;
const float ID_STRIDE_Y = 1.0f / IA_HEIGHT;
const vec2 ID_CORNER_OFFSETS[8] =
{
{ 0.0, 0.0}, // TopLeft
{ 0.0, 0.5*ID_STRIDE_Y}, // CenterLeft
{ 0.0, ID_STRIDE_Y}, // BotLeft
{ 0.5*ID_STRIDE_X, ID_STRIDE_Y}, // CenterBot
{ ID_STRIDE_X, ID_STRIDE_Y}, // BotRight
{ ID_STRIDE_X, 0.5*ID_STRIDE_Y}, // CenterRight
{ ID_STRIDE_X, 0.0}, // TopRight
{ 0.5*ID_STRIDE_X, 0.0}, // CenterTop
};
void main()
{
uint index = uint(gl_VertexID); // gl_VertexIndex ??
// Calculate vertex coordinates
uint vert_index = index & vertex_index_mask;
mat4 model_mtx = mtxFromCols(i_data0, i_data1, i_data2, i_data3);
vec4 worldPos = mul(model_mtx, vec4(verts[vert_index], 1.0));
gl_Position = mul(u_viewProj, worldPos);
world_pos = (worldPos.xyz); // pass world pos to fs
// // Calculate texture coordinates
// uint texture_corner = (index >> vertex_index_bits) & texture_corner_mask;
// uint texture_index = (index >> (texture_corner_bits + vertex_index_bits)) & texture_index_mask;
// uint texture_x = texture_index % TA_TEXTURES_PER_ROW;
// uint texture_y = texture_index / TA_TEXTURES_PER_ROW;
// tex_coord = vec2(texture_x * texture_stride_x, texture_y * texture_stride_y);
// tex_coord = tex_coord + corner_offsets[texture_corner];
// Texturing rework V2
// split index bits
uint texture_corner = (index >> vertex_index_bits) & texture_corner_mask;
uint id_index = (index >> (texture_corner_bits + vertex_index_bits)) & texture_index_mask;
// Calc id_coords (in id_atlas, for sampling [tex_id,light_id])
// in [0;ID_ATLAS_SIZE]
uint id_x = id_index % IA_WIDTH;
uint id_y = id_index / IA_HEIGHT;
// TODO rename tex_coord->id_coord
tex_coord = vec2(id_x * ID_STRIDE_X, id_y * ID_STRIDE_Y);
tex_coord = tex_coord + ID_CORNER_OFFSETS[texture_corner];
}

9
shaders/vs_lines.sc Normal file
View File

@@ -0,0 +1,9 @@
$input a_position
#include "bgfx_shader.sh"
#include "shaderlib.sh"
void main()
{
gl_Position = mul(u_modelViewProj, vec4(a_position, 1.0) );
}

74
src/CMakeLists.txt Normal file
View File

@@ -0,0 +1,74 @@
add_executable(spacegame
"factory.cpp"
"gameloop.cpp"
"graphics.cpp"
"lib/camera.cpp"
"lib/input.cpp"
"lib/stb_image.cpp"
"main.cpp"
"net/debug_server.cpp"
"net/network.cpp"
"renderer.cpp"
"space_input.cpp"
"space_math.cpp"
"util.cpp"
"world.cpp"
)
if(SPACEGAME_BUILD_SHADERS)
add_dependencies(spacegame shaders)
endif()
set_target_properties(spacegame PROPERTIES
OUTPUT_NAME spacegame
CXX_STANDARD 20
CXX_STANDARD_REQUIRED YES
CXX_EXTENSIONS NO
ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin/x86_64/$<CONFIG>
LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin/x86_64/$<CONFIG>
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin/x86_64/$<CONFIG>
)
target_compile_definitions(spacegame PRIVATE
$<$<CONFIG:Debug>:_DEBUG>
)
# -m64 - Build for a 64-bit machine
# -fno-exceptions - Disable exceptions (throw exception; => abort())
# -fno-rtti - Disable runtime type information (no reflection)
target_compile_options(spacegame PRIVATE
-m64
-fno-exceptions
-fno-rtti
# ${SPACEGAME_COMPILER_WARNINGS}
)
target_include_directories(spacegame PRIVATE
${PROJECT_SOURCE_DIR}/src
${PROJECT_SOURCE_DIR}/src/data
${PROJECT_SOURCE_DIR}/src/lib
${PROJECT_SOURCE_DIR}/src/net
${PROJECT_SOURCE_DIR}/3rdparty/stb
)
target_link_libraries(spacegame PRIVATE
glfw
bgfx::bx
bgfx::bimg
${BGFX}
Threads::Threads
)
################## release ###################
# Strip binary for release builds
if(CMAKE_BUILD_TYPE STREQUAL "Release" OR CMAKE_BUILD_TYPE STREQUAL "MinSizeRel")
add_custom_command(TARGET spacegame POST_BUILD
COMMAND echo "Stripping game executable"
COMMAND ${CMAKE_STRIP} --strip-all $<TARGET_FILE:spacegame>
VERBATIM
)
endif()

72
src/config.h Normal file
View File

@@ -0,0 +1,72 @@
#pragma once
#include <stdint.h>
/// Constants are used in many places
/// For ease of use we collect them all here
// TODO find reasonable numbers for these (take real use case measurements)
// how often do buffers need to grow? Are the initial values to small? too big?
namespace config
{
constexpr uint32_t MINIMAL_WINDOW_WIDTH = 800;
constexpr uint32_t MINIMAL_WINDOW_HEIGHT = 450;
enum LogLevel {
LOG_TRACE,
LOG_DEBUG,
LOG_INFO,
LOG_WARNING,
LOG_ERROR,
};
#ifdef NDEBUG
// in release mode
static LogLevel LOG_LEVEL = LOG_INFO;
#else
// in debug mode
static LogLevel LOG_LEVEL = LOG_DEBUG;
#endif
constexpr uint32_t INITIAL_NUM_GRIDS = 64;
constexpr uint32_t INITIAL_NUM_CHUNKS = 1024;
constexpr uint32_t INITIAL_NUM_BLOCKS = 1024 * 8; // all blocks in view distance at a time
constexpr uint32_t INITIAL_NUM_COMPONENTS = 16 * 24; // components are generated in all 24 orientation
constexpr uint32_t INITIAL_NUM_COMPONENT_TEXTURES = INITIAL_NUM_COMPONENTS * 6;
constexpr uint32_t INITIAL_NUM_BLOCK_MODELS = 256;
constexpr uint32_t AVERAGE_BLOCK_MODEL_SIZE = 64; // average number of indices in a block model
constexpr uint32_t INITIAL_BLOCK_MODEL_BUFFER_SIZE = INITIAL_NUM_BLOCK_MODELS * AVERAGE_BLOCK_MODEL_SIZE; // number of uint32_t indices
constexpr uint32_t INITIAL_BLOCK_SELECTION = 1024; // per frame visible and rendered blocks
constexpr uint32_t INITIAL_LINE_BUFFER_SIZE = INITIAL_BLOCK_MODEL_BUFFER_SIZE * 2; // *2 since a line consists of 2 vertices
/* Texture atlas */
// Vulkan spec 1.3 defines a minimum of 4096 for device cap maxImageDimension2D
// TODO actually query device caps
// ATTENTION must match shader
const uint32_t TA_WIDTH = 2; // patches in atlas
const uint32_t TA_HEIGHT = 2; // patches in atlas
const uint32_t TA_MAIN_PATCH_SIZE = 8; // textures per patch
const uint32_t TA_TEXTURE_SIZE = 4; // texel width per texture (aka how many texels per component)
constexpr uint32_t TA_INITIAL_NUMBER_PATCHES = 512; // TODO find reasonable default
const uint32_t TA_TEXTURES_PER_ROW = TA_WIDTH * TA_MAIN_PATCH_SIZE; // PERF should be power of 2 // per texture row
const uint32_t TA_TEXELS_PER_ROW = TA_TEXTURES_PER_ROW * TA_TEXTURE_SIZE; // per texel row
const uint32_t TA_TEXELS_PER_TEXTURE = TA_TEXTURE_SIZE * TA_TEXTURE_SIZE;
const uint32_t TA_TOTAL_TEXTURES = TA_WIDTH * TA_HEIGHT * TA_MAIN_PATCH_SIZE * TA_MAIN_PATCH_SIZE;
const uint32_t TA_TOTAL_TEXELS = TA_TOTAL_TEXTURES * TA_TEXELS_PER_TEXTURE;
// at least 8, since a full block must fit (and blocks have 8x8 components)
static_assert(TA_MAIN_PATCH_SIZE >= 8);
// shader can only address 2^19 textures
static_assert(TA_WIDTH * TA_HEIGHT * TA_MAIN_PATCH_SIZE * TA_MAIN_PATCH_SIZE < (1 << 19));
static uint64_t DEBUG_EXIT_AFTER_FRAME = 0; // set to 0 to disable
static bool DEBUG_SAVE_TEXTURE_ATLAS_TO_PNG = false;
static bool DEBUG_TRACE_LOG_FOREVER_PATCHES = true;
static bool DEBUG_TRACE_LOG_FIRST_FIT_BUFFER = false;
static bool DEBUG_TRACE_LOG_LINEAR_BUFFER = false;
static bool DEBUG_TRACE_LOG_SLOT_BUFFER = false;
static bool DEBUG_TRACE_LOG_SLOT_LIST = false;
}

37
src/data/chunk_storage.h Normal file
View File

@@ -0,0 +1,37 @@
/*#pragma once
#include "data/offset_vector.h"
#include <cstdint>
class ChunkStorage
{
private:
OffsetVector<OffsetVector<OffsetVector<uint32_t>>> data;
public:
ChunkStorage() = default;
~ChunkStorage() = default;
void insert(const uint32_t _x, const uint32_t _y, const uint32_t _z, const uint32_t _element)
{
logErr("ChunkStorage.insert is not implemented\n");
}
uint32_t at(const uint32_t _x, const uint32_t _y, const uint32_t _z)
{
logErr("ChunkStorage.at is not implemented\n");
return 0;
}
void remove(const uint32_t _x, const uint32_t _y, const uint32_t _z)
{
logErr("ChunkStorage.remove is not implemented\n");
}
void clean()
{
logErr("ChunkStorage.clean is not implemented\n");
// TODO shrink all offset vectors
}
};
*/

View File

@@ -0,0 +1,59 @@
#pragma once
#include "data/offset_vector.h"
#include <cstdint>
class ChunkStorage
{
private:
OffsetVector<OffsetVector<OffsetVector<uint32_t>>> data;
public:
ChunkStorage() = default;
~ChunkStorage() = default;
void insert(const uint32_t _x, const uint32_t _y, const uint32_t _z, const uint32_t _element)
{
data.reserve_at(_x).reserve_at(_y).reserve_at(_z) = _element;
}
uint32_t at(const uint32_t _x, const uint32_t _y, const uint32_t _z)
{
return data.get_at(_x).get_at(_y).get_at(_z);
}
void remove(const uint32_t _x, const uint32_t _y, const uint32_t _z)
{
auto& x = data;
auto& y = x.get_at(_x);
auto& z = y.get_at(_y);
z.remove_at(_z);
if (z.size() == 0)
{
y.remove_at(_y);
if (y.size() == 0)
{
x.remove_at(_x);
}
}
}
void clean()
{
logErr("ChunkStorage.clean is not implemented\n");
// TODO shrink all offset vectors
}
};
// has to be some kind of 3D array
// only stores uint32_t
// chunks need to be accessible by coordinates (x,y,z)
// should leave slots temporarily available
// only grow, do not shrink on remove
// only shrink on occasional clean()
// has to support negative offsets

202
src/data/first_fit_buffer.h Normal file
View File

@@ -0,0 +1,202 @@
#pragma once
#include <vector>
#include <cstring>
#include "util.h"
#include "config.h"
/// Provides first fit memory allocation
/// On demand defragmentation (if no more memory available)
/// Auto growing (if no more memory, even after defragmentation)
/// Auto updating of block references
template<class T>
class FirstFitBuffer
{
private:
const uint32_t OFFSET_FREE_MARKER = UINT32_C(1 << 31);
const uint32_t DEFRAGMENTATION_THRESHOLD = UINT32_MAX; // UINT32_C(1); // TODO find reasonable number, percentage?
std::vector<T> buffer;
std::vector<uint32_t> offsets; // holds all offsets, incl. the next free one
uint32_t num_free_elements; // removed sections that can be reclaimed by defragmentation
uint32_t modified_from; // inclusive
uint32_t modified_to; // !! exclusive
void defrag()
{
if (config::DEBUG_TRACE_LOG_FIRST_FIT_BUFFER)
logTrace("first_fit_buffer running defragmentation\n");
std::vector<uint32_t> ref_mapping(offsets.size(), UINT32_MAX);
uint32_t src_offset = 0;
uint32_t dst_offset = 0;
uint32_t num_elements = 0;
uint32_t offset = 0;
uint32_t next_offset = 0;
uint32_t section_size = 0;
uint32_t last_ref = 0;
// walk offsets
// on normal offset, increase num elements to copy
// on free, actually copy data
for (uint32_t ref = 0; ref < offsets.size() - 1; ref++)
{
offset = offsets[ref] & ~OFFSET_FREE_MARKER;
next_offset = offsets[(size_t)ref + 1] & ~OFFSET_FREE_MARKER;
if (offsets[ref] & OFFSET_FREE_MARKER)
{ // we hit a free section => copy anything up to this point
std::memmove(
(void*)(buffer.data() + dst_offset),
(void*)(buffer.data() + src_offset),
sizeof(T) * num_elements);
src_offset = next_offset;
dst_offset += num_elements;
num_elements = 0;
}
else
{ // is an in-use section => just add its size for next copy
section_size = next_offset - offset; // num elements at this offset
num_elements += section_size;
ref_mapping[ref /*orig ref*/] = last_ref /*new ref*/;
// update offsets
offsets[last_ref] = offset - (src_offset - dst_offset);
last_ref++;
}
}
// copy last section (if any)
std::memmove(
(void*)(buffer.data() + dst_offset),
(void*)(buffer.data() + src_offset),
sizeof(T) * num_elements);
// shrink offsets
offsets[last_ref] = offsets[(size_t)last_ref - 1] + section_size;
last_ref++;
offsets.resize(last_ref);
num_free_elements = 0;
modified_from = 0;
modified_to = offsets.back();
// At this point things have changed:
// buffer: data is now at difference offsets (though size remains the same)
// offsets: the ref into the offsets have changed (=> mapping)
// TODO update
// walk all world_blocks
// update their referenced blocks offsets
// update their gfx_ref with the mapping
logErr("first_fit_buffer defragmentation: mapping update not implemented\n");
}
public:
FirstFitBuffer(const uint32_t _initial_size) :
buffer(_initial_size), offsets(0), num_free_elements(0),
modified_from(UINT32_MAX), modified_to(0)
{
// add first free offset
offsets.push_back(0);
}
~FirstFitBuffer() = default;
// Returns reference NOT offset into buffer
uint32_t add(const T* _data, const uint32_t _num_elements)
{
uint32_t offset = offsets.back();
if (offset + _num_elements > (uint32_t)buffer.size())
{ // not enough space
// defragmentation worth it?
uint32_t total_free_elements = num_free_elements + ((uint32_t)buffer.size() - offsets.back());
if (total_free_elements >= _num_elements &&
total_free_elements >= DEFRAGMENTATION_THRESHOLD)
{
defrag();
// we know that there is enough space now
return add(_data, _num_elements); // tail recursive call
}
else
{ // defrag not sufficient or not worth it => grow the buffer
if (config::DEBUG_TRACE_LOG_FIRST_FIT_BUFFER)
logTrace("first_fit_buffer resizing from %d to %d\n", buffer.size(), buffer.size() * 2);
// notify caller about change in size
modified_from = (uint32_t)buffer.size();
buffer.resize(2 * buffer.size());
modified_to = (uint32_t)buffer.size();
// we know that there is enough space now
return add(_data, _num_elements); // tail recursive call
}
}
// copy data
std::memcpy(buffer.data() + offset, _data, sizeof(T) * _num_elements);
modified_from = std::min(modified_from, offset);
// add next offset
offsets.push_back(offset + _num_elements);
modified_to = std::max(modified_to, offsets.back());
return (uint32_t)offsets.size() - 2;
}
// Attention: new data MUST be of equal _num_elements as original
// Returns given _ref
uint32_t update(const uint32_t _ref, const T* _data)
{
uint32_t offset, num_elements;
get_offset(_ref, offset, num_elements);
// copy data
std::memcpy(buffer.data() + offset, _data, sizeof(T) * num_elements);
modified_from = std::min(modified_from, offset);
modified_to = std::max(modified_to, offset + num_elements);
return _ref;
}
void remove(const uint32_t _ref)
{
num_free_elements += ((offsets[(size_t)_ref + 1] & ~OFFSET_FREE_MARKER) - offsets[_ref]);
// just mark offset as free
offsets[_ref] |= OFFSET_FREE_MARKER;
}
void get_offset(const uint32_t _ref, uint32_t& _out_offset, uint32_t& _out_num_elements)
{
_out_offset = offsets[_ref];
_out_num_elements = offsets[(size_t)_ref + 1] - _out_offset;
}
bool wasModified(uint32_t& _out_offset, uint32_t& _out_num_elements)
{
_out_offset = modified_from;
_out_num_elements = modified_to - modified_from;
return modified_to > modified_from;
}
void clearModified()
{
modified_from = UINT32_MAX;
modified_to = 0;
}
T* data()
{
return buffer.data();
}
};

93
src/data/linear_buffer.h Normal file
View File

@@ -0,0 +1,93 @@
#pragma once
#include <vector>
#include "config.h"
template<class T>
class LinearBuffer // aka transient buffer
{
private:
std::vector<T> buffer;
uint16_t num_elements; // bgfx only allows up to 16-bit number of elements
uint16_t modified_size;
bool modified;
bool resized;
public:
LinearBuffer(const uint32_t _initial_size) :
buffer(_initial_size), num_elements(0), modified_size(0), modified(false), resized(false)
{ }
~LinearBuffer() = default;
// TODO support multi-threading
// Reserves a number of elements, that MUST be filled (use update(..))
// Returns an offset to be passed to update(..)
uint32_t reserve(uint32_t _num_slots)
{
modified = true;
uint32_t offset = num_elements;
// sanity check
if ((uint32_t)num_elements + _num_slots > UINT16_MAX)
{
logErr("linear_buffer elements is greater than uint16. This will now break things.\n");
}
num_elements += _num_slots;
if (num_elements > buffer.size())
{ // need to grow
if (config::DEBUG_TRACE_LOG_LINEAR_BUFFER)
logTrace("linear_buffer resizing from %d to %d\n", buffer.size(), 2 * buffer.size());
buffer.resize(2 * buffer.size());
// notify caller
modified_size = (uint32_t)buffer.size();
resized = true;
}
return offset;
}
void update(uint32_t _offset, const T& _element)
{
modified = true;
buffer[_offset] = _element;
}
// Adds an element at the end of the buffer
void add(const T& _element)
{
update(reserve(1), _element);
}
uint16_t get_num_elements()
{
return num_elements;
}
bool wasModified(uint32_t& _out_offset, uint32_t& _out_num_elements, bool& _out_resized)
{
_out_offset = 0;
_out_num_elements = std::max(num_elements, modified_size);
_out_resized = resized;
return modified;
}
T* data()
{
return buffer.data();
}
// Clears entire buffer
void clear()
{
modified = false;
resized = false;
modified_size = 0;
num_elements = 0;
}
};

158
src/data/offset_vector.h Normal file
View File

@@ -0,0 +1,158 @@
/*#pragma once
#include <vector>
#include <cassert>
#include "util.h"
// A dynamic container, allowing negative indices and wrap around
// Note: indices alway centered around 0 (ie. min_index=5 max_index=8 still allocates [0;8])
// Attention: The internal storage capacity will be at least [min_index; 0] + [0; max_index]
template<typename Ty>
class OffsetVector
{
private:
std::vector<Ty> data;
int32_t min_index; // smallest in-use index
int32_t max_index;
Ty invalid;
// Calculates a wrap around index into data
int32_t wrap_index(int32_t _index)
{
//return (_index + data.size()) % data.size();
return (_index + (int32_t)data.size()) & ((int32_t)data.size() - 1); // equal since size is always power of 2
}
void grow(uint32_t _required)
{
uint32_t size = (uint32_t)data.size();
size = (size == 0) ? 1 : size;
uint32_t old_size = size;
uint32_t old_wrap_min_index = wrap_index(min_index); // needs to be done before resize
while (size < _required)
size *= 2;
data.resize(size);
// move data
// Note: elements from [0;max_index] always stay in the same location
// only elements from [min_index;data.size()-1] may need to be moved
if (min_index < 0)
{
// we swap new "empty" elements from the back with the old ones that need moving
// back to front in order not to copy overlapping memory regions
// we use std::swap because std::move is not guaranteed to leave the old element in a
// valid state (in the case its a vector or so)
uint32_t dst = (uint32_t)data.size();
for (uint32_t src = old_size - 1; src >= old_wrap_min_index; --src)
{
--dst;
std::swap(data[src], data[dst]);
}
}
}
public:
// When deleting an element, its slot is set to _invalid.
// This can be a default constructed object.
OffsetVector(Ty _invalid = Ty()) : invalid(_invalid), min_index(0), max_index(0), data(0, _invalid) {};
~OffsetVector() = default;
// reserves the give index
// Returns reference to the slot
Ty& reserve_at(const int32_t _index)
{
if (data.size() == 0)
{
grow(1);
}
if (_index > max_index)
{ // need to expand in positive direction
int32_t free = (int32_t)data.size() - (max_index - min_index + 1);
if (_index > max_index + free)
{ // need to grow
grow(_index - min_index + 1);
}
max_index = _index; // advance max_index
}
if (_index < min_index)
{ // need to expand in negative direction
int32_t free = (int32_t)data.size() - (max_index - min_index + 1);
if (_index < min_index - free)
{ // need to grow
grow(max_index - _index + 1);
}
min_index = _index; // advance min_index
}
return data[wrap_index(_index)];
}
// Convenience function. Should probably use reserve_at
// May overwrite an existing element
void insert_at(const int32_t _index, const Ty _element)
{
reserve_at(_index) = _element;
}
// May return a "invalid" aka not inserted element
Ty& get_at(const int32_t _index)
{
assert(_index >= min_index);
assert(_index <= max_index);
return data[wrap_index(_index)];
}
// Overwrites the element at _index with the _invalid
void remove_at(const int32_t _index)
{
assert(_index >= min_index);
assert(_index <= max_index);
// Note: we do not erase from vector, we just set min/max_index, and reset the element to _invalid
data[wrap_index(_index)] = invalid;
if (_index == min_index)
{ // we have to increase min_index
while (min_index < 0 && data[wrap_index(min_index)] == invalid)
{
++min_index;
}
}
if (_index == max_index)
{ // we have to decrease max_index
while (max_index > 0 && data[wrap_index(max_index)] == invalid)
{
--max_index;
}
}
}
uint32_t size()
{
//return (min_index == max_index && data[0] == invalid) ? 0 : max_index - min_index + 1;
if (min_index == max_index)
{
assert(min_index == 0);
return (data[0] != invalid) ? 1 : 0;
}
return max_index - min_index + 1;
}
void shrink_to_fit()
{ // TODO
logErr("offset_vector.shrink_to_fit not implemented\n");
}
};
*/

83
src/data/queue.h Normal file
View File

@@ -0,0 +1,83 @@
#pragma once
#include <vector>
#include "util.h"
// simple single-threaded queue
template<class T>
class Queue
{
private:
std::vector<T> buffer;
size_t next_push; // points to next free slot to be pushed
size_t next_pop; // points to next data slot to be popped
size_t size_mask;
public:
Queue(size_t _initial_size = 2) :
buffer(0), next_push(0), next_pop(0), size_mask(1)
{
// Enforce size is power of 2
if (_initial_size < 2) { _initial_size = 2; }
size_t size = 2;
while (_initial_size > size)
{
size <<= 1;
size_mask = (size_mask << 1) | 0b1;
}
buffer.resize(size);
}
~Queue() = default;
inline size_t wrap(size_t idx) {
return idx & size_mask;
//return idx % buffer.size();
}
void push(const T _t)
{
buffer[next_push] = _t;
// check if theres is space available
if (wrap(next_push + 1) == next_pop)
{ // no more space => need to grow
buffer.resize(buffer.size() << 1);
size_mask = (size_mask << 1) | 0b1;
// move potential wrapped data at the end
if (next_pop > next_push)
{
size_t src = (buffer.size() >> 1) - 1; // old size -1
size_t dst = buffer.size() - 1;
while (src >= next_pop)
{
std::swap(buffer[dst], buffer[src]);
--dst;
--src;
}
next_pop = dst + 1;
}
// advance next_push
next_push = wrap(next_push + 1);
return;
}
// advance next_push
next_push = wrap(next_push + 1);
}
// returns true if at least one element is available
bool can_pop() {
return next_pop != next_push;
}
T pop()
{
T t = buffer[next_pop];
next_pop = wrap(next_pop + 1);
return t;
}
};

120
src/data/slot_buffer.h Normal file
View File

@@ -0,0 +1,120 @@
#pragma once
#include <stdexcept>
#include <vector>
#include "util.h"
#include "config.h"
// TODO specialization if T==int (or atleast sizeof(T) >= sizeof(int))
// store freelist in-place in the buffer itself
// free points to 0 (aka invalid) => use net_free/offset to find next free slot
// on remove(5), point 5 to where ever free points, point free to 5
// on add, just take the first value from free (or next_free if free==0)
// Auto growing buffer
// returns slot reference that stays valid until element is removed
// empty slots are reused
template<class T>
class SlotBuffer
{
private:
std::vector<T> buffer;
uint32_t offset; // points to next free slot
std::vector<int32_t> free;
uint32_t modified_from; // inclusive
uint32_t modified_to; // inclusive
public:
SlotBuffer(const uint32_t _initial_size) :
buffer(_initial_size), offset(0), free(0),
modified_from(UINT32_MAX), modified_to(0)
{
if (_initial_size == 0 || _initial_size == UINT32_MAX) {
std::abort();
}
}
~SlotBuffer() = default;
// Reserves a slot. Data MUST be initialized!
// Returns slot in _out_ref.
// Returns reference to element.
T& add(uint32_t& _out_slot)
{
uint32_t slot = 0;
if (!free.empty())
{ // reuse slot
slot = free.back();
free.pop_back();
}
else if (offset < buffer.size())
{ // use new slot
slot = offset;
offset++;
}
else
{ // no free slot => need to grow
if (config::DEBUG_TRACE_LOG_SLOT_BUFFER)
logTrace("slot_buffer resizing from %d to %d\n", buffer.size(), 2 * buffer.size());
// notify caller about change in size
modified_from = (uint32_t)buffer.size();
buffer.resize(2 * buffer.size());
modified_to = (uint32_t)buffer.size() - 1;
// now there is enough space
slot = offset;
offset++;
}
modified_from = std::min(modified_from, slot);
modified_to = std::max(modified_to, slot);
_out_slot = slot;
return buffer[slot];
}
// Provides direct access to the element in _slot
// Does NOT perform bound checks.
// Element will considered modified.
inline T& at(const uint32_t _slot)
{
modified_from = std::min(modified_from, _slot);
modified_to = std::max(modified_to, _slot);
return buffer[_slot];
}
// Provides direct read-only access to the element in _slot
// Does NOT perform bound checks.
inline const T& at_const(const uint32_t _slot)
{
return buffer[_slot];
}
void remove(const uint32_t _slot)
{
free.push_back(_slot);
}
bool wasModified(uint32_t& _out_offset, uint32_t& _out_num_elements)
{
_out_offset = modified_from;
_out_num_elements = modified_to - modified_from + 1;
return modified_to >= modified_from;
}
void clearModified()
{
modified_from = UINT32_MAX;
modified_to = 0;
}
T* data()
{
return buffer.data();
}
};

196
src/data/slot_list.h Normal file
View File

@@ -0,0 +1,196 @@
#pragma once
#include <stdexcept>
#include <vector>
#include "util.h"
#include <assert.h>
// Auto growing buffer
// returns slot reference that remains valid until element is removed
// removed slots are reused
// supports element iteration (in random order)
template<class T>
class SlotList
{
private:
typedef struct header {
uint32_t prev;
uint32_t next;
} Header;
// contains links for every used slot to the next used slot
// or from a free slot to the next free slot
std::vector<Header> header_buffer;
// contains the used data
std::vector<T> data_buffer;
// points to the next available/free slot
uint32_t next_free;
uint32_t modified_from; // inclusive
uint32_t modified_to; // inclusive
const uint32_t first_free_slot = 0;
const uint32_t first_used_slot = 1;
// removes element at _slot from its current list (free or used)
void list_remove(const uint32_t _slot) {
assert(_slot != first_free_slot);
assert(_slot != first_used_slot);
assert(_slot != UINT32_MAX);
header_buffer[header_buffer[_slot].prev].next = header_buffer[_slot].next;
header_buffer[header_buffer[_slot].next].prev = header_buffer[_slot].prev;
}
// adds element at _slot to free list
void list_add_free(const uint32_t _slot) {
assert(_slot != first_free_slot);
assert(_slot != first_used_slot);
assert(_slot != UINT32_MAX);
header_buffer[_slot].next = header_buffer[first_free_slot].next;
header_buffer[_slot].prev = first_free_slot;
header_buffer[header_buffer[_slot].next].prev = _slot;
header_buffer[first_free_slot].next = _slot;
}
// adds element at _slot to used list
void list_add_used(const uint32_t _slot) {
assert(_slot != first_free_slot);
assert(_slot != first_used_slot);
assert(_slot != UINT32_MAX);
header_buffer[_slot].next = header_buffer[first_used_slot].next;
header_buffer[_slot].prev = first_used_slot;
header_buffer[header_buffer[_slot].next].prev = _slot;
header_buffer[first_used_slot].next = _slot;
}
public:
SlotList(const uint32_t _initial_size) :
header_buffer(_initial_size), data_buffer(_initial_size),
next_free(2), modified_from(UINT32_MAX), modified_to(0)
{
if (_initial_size < 4 || _initial_size == UINT32_MAX) {
std::abort();
}
header_buffer[first_free_slot].prev = first_free_slot;
header_buffer[first_free_slot].next = first_free_slot;
header_buffer[first_used_slot].prev = first_used_slot;
header_buffer[first_used_slot].next = first_used_slot;
}
~SlotList() = default;
// Reserves a slot. Data is NOT automatically initialized!
// Returns slot in _out_ref.
// Returns reference to element.
// Element will considered modified.
T& add(uint32_t& _out_slot)
{
uint32_t slot = 0;
if (header_buffer[first_free_slot].next != first_free_slot)
{ // reuse slot
slot = header_buffer[first_free_slot].next;
// remove from free list
list_remove(slot);
}
else if (next_free < data_buffer.size())
{ // use new slot
slot = next_free;
next_free++;
}
else
{ // no free slot => need to grow
if (config::DEBUG_TRACE_LOG_SLOT_LIST)
logTrace("slot_list resizing from %d to %d\n", data_buffer.size(), 2 * data_buffer.size());
// notify caller about change in size
modified_from = (uint32_t)data_buffer.size();
header_buffer.resize(2 * data_buffer.size());
data_buffer.resize(2 * data_buffer.size());
modified_to = (uint32_t)data_buffer.size() - 1;
// now there are free slots
slot = next_free;
next_free++;
}
modified_from = std::min(modified_from, slot);
modified_to = std::max(modified_to, slot);
// add to used list
list_add_used(slot);
_out_slot = slot;
return data_buffer[slot];
}
// Provides direct access to the element in _slot
// Does NOT perform bound checks.
// Element will considered modified.
inline T& at(const uint32_t _slot)
{
modified_from = std::min(modified_from, _slot);
modified_to = std::max(modified_to, _slot);
return data_buffer[_slot];
}
// Provides direct read-only access to the element in _slot
// Does NOT perform bound checks.
inline const T& at_const(const uint32_t _slot) const
{
return data_buffer[_slot];
}
void remove(const uint32_t _slot)
{
// remove from used list
list_remove(_slot);
// add to free list
list_add_free(_slot);
}
// Returns true if there are no elements in the container
inline bool empty() const {
return header_buffer[first_used_slot].next == first_used_slot;
}
// Returns slot of a random element
// Behaviour is undefined, if container is empty.
// Note: Use this function together with next() to iterate all elements
inline uint32_t first() const {
return header_buffer[first_used_slot].next;
}
// Returns true if element at _slot has a next element
inline bool has_next(const uint32_t _slot) const {
return header_buffer[_slot].next != first_used_slot;
}
// Returns slot of the next element
// Behaviour is undefined, if _slot is invalid or 'has_next(_slot) == false'.
// Note: Use this function together with first() to iterate all elements
inline uint32_t next(const uint32_t _slot) const {
return header_buffer[_slot].next;
}
// Returns interval of internal data buffer which was modified (if any)
bool wasModified(uint32_t& _out_offset, uint32_t& _out_num_elements) const
{
_out_offset = modified_from;
_out_num_elements = modified_to - modified_from + 1;
return modified_to >= modified_from;
}
// Clears internal modified interval
void clearModified()
{
modified_from = UINT32_MAX;
modified_to = 0;
}
// Returns direct access to internal data buffer
T* data()
{
return data_buffer.data();
}
};

101
src/data/slot_queue.h Normal file
View File

@@ -0,0 +1,101 @@
#pragma once
#include <vector>
#include <mutex>
#include "util.h"
// Auto growing, slot-based, thread safe queue
// Attention: Thread-safety applies only for 1 producer and 1 consumer thread!
template<class T>
class SlotQueue
{
private:
std::vector<T> buffer;
size_t next_push; // points to next free slot to be pushed
size_t next_pop; // points to next data slot to be popped
size_t size_mask;
std::mutex mutex;
public:
SlotQueue(size_t _initial_size = 2) :
buffer(0), next_push(0), next_pop(0), size_mask(1)
{
// Enforce size is power of 2
if (_initial_size < 2) { _initial_size = 2; }
size_t size = 2;
while (_initial_size > size)
{
size <<= 1;
size_mask = (size_mask << 1) | 0b1;
}
buffer.resize(size);
}
~SlotQueue() = default;
inline size_t wrap(size_t idx) {
return idx & size_mask;
//return idx % buffer.size();
}
// Returns a pointer to the next element at the end of the queue, to be produced
// Returned pointer is never NULL
T* begin_push()
{
return &buffer[next_push];
}
// Publishes last (with begin_push()) produced element
void end_push()
{
// check if theres is space available
if (wrap(next_push + 1) == next_pop)
{ // no more space => need to grow
mutex.lock();
buffer.resize(buffer.size() << 1);
size_mask = (size_mask << 1) | 0b1;
// move potential wrapped data at the end
if (next_pop > next_push)
{
size_t src = (buffer.size() >> 1) - 1; // old size -1
size_t dst = buffer.size() - 1;
while (src >= next_pop)
{
std::swap(buffer[dst], buffer[src]);
--dst;
--src;
}
next_pop = dst + 1;
}
// advance next_push
next_push = wrap(next_push + 1);
mutex.unlock();
return;
}
// advance next_push
mutex.lock();
next_push = wrap(next_push + 1);
mutex.unlock();
}
// Returns pointer to the element at the top of the queue, to be consumed
// Returns NULL if there is no element in the queue
// Can be used to just "peek" at the top element
T* begin_pop()
{
return (next_pop != next_push) ? &buffer[next_pop] : NULL;
}
// Releases last (with begin_pop()) consumed element
// MUST only be called after a valid (non NULL) 'begin_pop()'
void end_pop()
{
mutex.lock();
next_pop = wrap(next_pop + 1);
mutex.unlock();
}
};

63
src/data/texel_buffer.h Normal file
View File

@@ -0,0 +1,63 @@
#pragma once
#include <stdexcept>
#include <vector>
#include "util.h"
#include "../space_math.h"
#include <bx/bx.h>
class TexelBuffer
{
private:
std::vector<Texel> buffer;
uint32_t modified_from; // inclusive
uint32_t modified_to; // inclusive
public:
TexelBuffer(const uint32_t _size) :
buffer(_size), modified_from(UINT32_MAX), modified_to(0)
{
if (_size == 0) {
std::abort();
}
}
~TexelBuffer() = default;
// copies 4x4 texels from _data into the buffer at _texel_id
void update_texels(const uint32_t _texel_id, const Texel *_data) {
bx::memCopy(buffer.data() + _texel_id,
_data,
4 * sizeof(Texel));
bx::memCopy(buffer.data() + _texel_id + config::TA_TEXELS_PER_ROW,
_data + 4,
4 * sizeof(Texel));
bx::memCopy(buffer.data() + _texel_id + config::TA_TEXELS_PER_ROW * 2,
_data + 8,
4 * sizeof(Texel));
bx::memCopy(buffer.data() + _texel_id + config::TA_TEXELS_PER_ROW * 3,
_data + 12,
4 * sizeof(Texel));
modified_from = std::min(modified_from, _texel_id);
modified_to = std::max(modified_to, _texel_id + config::TA_TEXELS_PER_ROW * 4);
}
bool wasModified(uint32_t& _out_offset, uint32_t& _out_num_elements)
{
_out_offset = modified_from;
_out_num_elements = modified_to - modified_from + 1;
return modified_to >= modified_from;
}
void clearModified()
{
modified_from = UINT32_MAX;
modified_to = 0;
}
Texel* data()
{
return buffer.data();
}
};

463
src/data/texture_atlas.h Normal file
View File

@@ -0,0 +1,463 @@
#pragma once
#include <stdexcept>
#include <vector>
#include "util.h"
#include "assert.h"
#include "slot_list.h"
#include "../space_math.h"
#include "texel_buffer.h"
#include "../config.h"
#include "stb_image.h"
#include "stb_image_write.h"
#include "util.h"
#include "../3rdparty/emilib/hash_map.hpp"
// Manages texturing data in a combined atlas
// all textures are stored in patches
// a patch is a region in the atlas
// a patch consists of 8x8 or smaller textures
// a texture consists of 4x4 texels
// each texture is addressed by a texture id
// TODO add normal mapping
// TODO OPT: Do not update entire rows of the atlas, but more localized blocks
// Ex.: Track modified main textures (sorted list), then when modified() is called,
// combine them into rectangular block.
// TODO consider
// Sidenote: if we de-couple the actual tex_atlas and the id_atlas
// we can generate patches in id_atlas only, without loading onto gpu right away
// then later on-demand copy texture into tex_atlas (and thereby onto gpu)
// Note to future self: TextureAtlas 2.0
// If we ever want to upgrade this implementation we should try to do so by
// considering triangular textures instead of quad textures
// Large trigs waste precious atlas space
// Would need to consider double-trigs
// => Maybe keep current api and detect empty patches, then
// just increase patch ref count
// and add sub-patches to pool of free patches (ensure freeing works!)
// This would imply proper patch-parenting
// Add parent patch_id to each patch and replace main patch logic with this
//
// Note: This also breaks our hashing, except we no actually consider the trig shape somehow
// Maybe combine this sub-texture deduplication?
// Helper classes for the texture deduplication
struct TextureHash {
const uint16_t width = 0;
const uint16_t height = 0;
const uint32_t stride = 0; // way too big, but alignment
const uint32_t* texture_ids = nullptr;
TextureHash() = default;
// stride describes how to index into texture_ids
// aka should be '8' for temporary 8x8 arrays and config::TA_TEXTURES_PER_ROW
TextureHash(const uint16_t _width, const uint16_t _height, const uint32_t _stride, const uint32_t* _texture_ids) : width(_width), height(_height), stride(_stride), texture_ids(_texture_ids) {};
bool operator==(const TextureHash &_o) const {
if (width != _o.width || height != _o.height)
return false;
bool equal = true;
for (int y = 0; y < height; y++)
for (int x = 0; x < width; x++)
{
equal &= (texture_ids[x + y * stride] == _o.texture_ids[x + y * stride]);
}
return equal;
};
};
const int TextureHashSize = sizeof(TextureHash);
template <>
struct std::hash<TextureHash>
{
size_t operator()(const TextureHash &_v) const noexcept
{
std::size_t h=0;
hash_combine(h, _v.width, _v.height);
for (int y = 0; y < _v.height; y++)
for (int x = 0; x < _v.width; x++)
hash_combine(h, _v.texture_ids[x + y * _v.stride]);
return h;
}
};
class TextureAtlas
{
private:
typedef struct patch {
uint32_t refCount : 8 = 0; // steal some bits from texture_id for alignment reasons and since we don't need that many
uint32_t texture_id : 24 = 0; // offset to textures in atlas // TODO maybe store x&y offsets instead
uint16_t width = 0; // number of textures
uint16_t height = 0; // number of textures
} Patch;
static_assert(sizeof(Patch) == 8);
static_assert(config::TA_TOTAL_TEXTURES < (1 << 24));
// main patch == full 8x8 patch
typedef struct main_patch {
uint32_t used_count; // can be merged when ==0
std::vector<uint32_t> free_patches; // refs to patches
} MainPatch;
SlotList<Patch> patches = SlotList<Patch>(config::TA_INITIAL_NUMBER_PATCHES);
// "second" atlas with texture_ids (corresponding to the loaded texels) for deduplication
uint32_t textures[config::TA_TOTAL_TEXTURES] = {};
emilib::HashMap<TextureHash, uint32_t /*patch_id*/> texture_lookup;
// free patches sorted by size
// bins[0] = 1x1
// bins[1] = 1x2
// bins[2] = 1x3
// ...
// bins[TA_MAIN_PATCH_SIZE] = 2x1
std::vector<uint32_t> bins[config::TA_MAIN_PATCH_SIZE * config::TA_MAIN_PATCH_SIZE]; // holds refs to patches
// hold refs to sub patches for merging purposes
MainPatch main_patches[config::TA_WIDTH * config::TA_HEIGHT];
TexelBuffer texels = TexelBuffer(config::TA_TOTAL_TEXELS);
uint32_t main_patch_id_2_texture_id(uint32_t _id) {
const uint32_t x = _id % config::TA_WIDTH;
const uint32_t y = _id / config::TA_WIDTH;
return (y * config::TA_MAIN_PATCH_SIZE * config::TA_TEXTURES_PER_ROW) + (x * config::TA_MAIN_PATCH_SIZE);
}
uint32_t texture_id_2_main_patch_id(uint32_t _id) {
uint32_t x = _id % config::TA_TEXTURES_PER_ROW; // texture offsets
uint32_t y = _id / config::TA_TEXTURES_PER_ROW;
x /= config::TA_MAIN_PATCH_SIZE; // main patch offsets
y /= config::TA_MAIN_PATCH_SIZE;
return y * config::TA_WIDTH + x;
}
uint32_t texture_id_2_texel_id(uint32_t _id) {
uint32_t x = _id % config::TA_TEXTURES_PER_ROW; // texture offsets
uint32_t y = _id / config::TA_TEXTURES_PER_ROW;
return (y * config::TA_TEXTURES_PER_ROW * config::TA_TEXELS_PER_TEXTURE) +
(x * config::TA_TEXTURE_SIZE);
}
// Saves the current texture atlas to a rgba8 texture (.png)
// Starts enumerating at 0 and incrementing on subsequent calls
// Resets suffix on startup
// Overwrites existing files
uint32_t counter = 0;
void save_texture_atlas_to_png()
{
std::string filename = "atlas";
std::filesystem::create_directories(filename);
filename += "/";
filename += std::to_string(counter);
filename += ".png";
const int width = config::TA_TEXELS_PER_ROW;
const int height = config::TA_HEIGHT * config::TA_MAIN_PATCH_SIZE * config::TA_TEXTURE_SIZE;
stbi_write_png(filename.c_str(), width, height, 4 /* channels (RGBA) */, texels.data(), config::TA_TEXELS_PER_ROW * sizeof(Texel));
counter++;
}
public:
TextureAtlas() = default;
~TextureAtlas() = default;
void init() {
texture_lookup.reserve(config::TA_INITIAL_NUMBER_PATCHES);
// create main patches
for (int i = (config::TA_WIDTH * config::TA_HEIGHT) - 1; i >= 0; i--) {
uint32_t patch_id;
Patch &patch = patches.add(patch_id);
patch.texture_id = main_patch_id_2_texture_id(i);
patch.width = config::TA_MAIN_PATCH_SIZE;
patch.height = config::TA_MAIN_PATCH_SIZE;
// add to bins
bins[(config::TA_MAIN_PATCH_SIZE * config::TA_MAIN_PATCH_SIZE) - 1].push_back(patch_id);
// add to main patches
main_patches[i].used_count = 0;
main_patches[i].free_patches.push_back(patch_id);
}
}
private: // TODO move implementations into atlas.cpp and fix this
// finds fitting patch in atlas
// Supports deduplication
// returns patch_id
uint32_t get_new_patch(const uint8_t _width, const uint8_t _height) {
assert(_width > 0);
assert(_height > 0);
assert(_width <= 8);
assert(_height <= 8);
// search bins
uint32_t bin = (_width-1) * 8 + (_height-1);
while (bin < 64 && bins[bin].size() == 0) {
bin++; // try next bigger one
}
if (bin == 64) {
logErr("TextureAtlas overflow\n");
std::abort(); // Atlas overflow
}
// remove from bins
uint32_t patch_id = bins[bin].back();
bins[bin].pop_back();
Patch &patch = patches.at(patch_id);
// remove from main patch
MainPatch &main_patch = main_patches[texture_id_2_main_patch_id(patch.texture_id)];
main_patch.used_count++;
auto it = std::find(main_patch.free_patches.begin(), main_patch.free_patches.end(), patch_id);
assert(it != main_patch.free_patches.end());
main_patch.free_patches.erase(it);
// cut it up
// [X|ooo]
// [-----]
// [ooooo]
// [ooooo]
// [ooooo]
// & add any extra back into bins
// extra to the right
if (_width < patch.width) {
uint32_t right_patch_id;
Patch& right_patch = patches.add(right_patch_id);
right_patch.width = patch.width - _width;
assert(right_patch.width > 0 && right_patch.width < config::TA_MAIN_PATCH_SIZE);
right_patch.height = _height;
right_patch.texture_id = patch.texture_id + _width;
// add to bins
const uint32_t bin = ((right_patch.width-1) * config::TA_MAIN_PATCH_SIZE) + (right_patch.height-1);
bins[bin].push_back(right_patch_id);
// add to main patch
main_patch.free_patches.push_back(right_patch_id);
}
// extra below
if (_height < patch.height) {
uint32_t bottom_patch_id;
Patch& bottom_patch = patches.add(bottom_patch_id);
bottom_patch.width = patch.width;
bottom_patch.height = patch.height - _height;
assert(bottom_patch.height > 0 && bottom_patch.height < config::TA_MAIN_PATCH_SIZE);
bottom_patch.texture_id = patch.texture_id + (_height * config::TA_TEXTURES_PER_ROW);
// add to bins
const uint32_t bin = ((bottom_patch.width-1) * config::TA_MAIN_PATCH_SIZE) + (bottom_patch.height-1);
bins[bin].push_back(bottom_patch_id);
// add to main patch
main_patch.free_patches.push_back(bottom_patch_id);
}
patch.refCount = 1;
patch.width = _width;
patch.height = _height;
return patch_id;
}
// copies a texture into a patch
// _data MUST point to valid memory (depending on configured texture size)
// Note: always copies a full texture (aka TA_TEXTURE_SIZE*TA_TEXTURE_SIZE individual texels)
// Returns texture_id for gfx::vertex buffer generation
uint32_t update_texture(const Patch &patch, const uint8_t _offset_x, const uint8_t _offset_y, const uint32_t* _component_texture_ids) {
const Texel* data = gfx::get_component_texture(_component_texture_ids[_offset_x + _offset_y * 8]);
const uint32_t texture_id = patch.texture_id + _offset_y * config::TA_TEXTURES_PER_ROW + _offset_x;
assert(_offset_x < patch.width);
assert(_offset_y < patch.height);
// load texture_ids into "second" atlas for deduplication
textures[texture_id] = _component_texture_ids[_offset_x + _offset_y * 8];
// load texels into atlas
const uint32_t texel_id = texture_id_2_texel_id(texture_id);
texels.update_texels(texel_id, data);
#ifndef NDEBUG
if (config::DEBUG_SAVE_TEXTURE_ATLAS_TO_PNG)
save_texture_atlas_to_png();
#endif
return texture_id;
}
// returns true on success, false otherwise
// _component_texture_ids expected to be inside an array of 8*8
// it is guaranteed not to access other _component_texture_ids as confined per _width and _height though
bool deduplicate_patch(const TextureHash& _key, uint32_t& _out_patch_id)
{
auto it = texture_lookup.find(_key);
if (it != texture_lookup.end())
{
_out_patch_id = it->second;
// increase ref count, but do not overflow
// once we hit the limit, we consider
// the patch promoted to an forever patch
Patch& patch = patches.at(_out_patch_id);
if (patch.refCount < 255)
patch.refCount++;
if (config::DEBUG_TRACE_LOG_FOREVER_PATCHES && patch.refCount == 255)
logTrace("[TextureAtlas] Patch %d hit a refcount of 255 and was promoted to a forever patch.\n", _out_patch_id);
return true;
}
return false;
}
public: // TODO move implementations into atlas.cpp and fix this
// Finds fitting patch in atlas
// Supports deduplication
// returns patch_id
// _component_texture_ids expected to be inside an array of 8*8
// it is guaranteed not to access other _component_texture_ids as confined per _width and _height though
uint32_t add_patch(const uint8_t _width, const uint8_t _height, const uint32_t* _component_texture_ids) {
assert(_width > 0);
assert(_height > 0);
assert(_width <= 8);
assert(_height <= 8);
uint32_t patch_id = 0;
// try to de-duplicate the given texture
if (deduplicate_patch(TextureHash(_width, _height, 8, _component_texture_ids), patch_id))
return patch_id;
// else get new patch and copy data
patch_id = get_new_patch(_width, _height);
const Patch &patch = patches.at_const(patch_id);
// 8*8 texels
for (int x = 0; x < _width; x++)
for (int y = 0; y < _height; y++)
update_texture(patch, x, y, _component_texture_ids);
// add this patch the texture_lookup
texture_lookup.insert_unique(std::move(TextureHash(_width, _height, config::TA_TEXTURES_PER_ROW, &textures[patch.texture_id])), std::move(patch_id));
return patch_id;
}
// Returns 8 texture ids (for vertex buffer generation) of given patch
void get_patch_texture_ids(const uint32_t _patch_id, uint32_t* _out_texture_ids)
{
Patch &patch = patches.at(_patch_id);
_out_texture_ids[TexCorner::TopLeft] = patch.texture_id;
// TexCorner for sampling is = (trigSize%2==0) ? TL : CL;
_out_texture_ids[TexCorner::CenterLeft] = patch.texture_id + ((patch.height / (uint32_t)2) * config::TA_TEXTURES_PER_ROW);
_out_texture_ids[TexCorner::BotLeft] = patch.texture_id + ((patch.height - 1) * config::TA_TEXTURES_PER_ROW);
// TexCorner for sampling is = (trigSize%2==0) ? BL : CB;
_out_texture_ids[TexCorner::CenterBot] = _out_texture_ids[TexCorner::BotLeft] + (patch.width / (uint32_t)2);
_out_texture_ids[TexCorner::BotRight] = _out_texture_ids[TexCorner::BotLeft] + (patch.width - 1);
// TexCorner for sampling is = (trigSize%2==0) ? TR : CR;
_out_texture_ids[TexCorner::CenterRight] = _out_texture_ids[TexCorner::CenterLeft] + (patch.width - 1);
_out_texture_ids[TexCorner::TopRight] = patch.texture_id + (patch.width - 1);
// TexCorner for sampling is = (trigSize%2==0) ? TL : CT;
_out_texture_ids[TexCorner::CenterTop] = patch.texture_id + (patch.width / (uint32_t)2);
}
// frees this patch's space in the atlas
void remove_patch(const uint32_t _patch_id) {
Patch &patch = patches.at(_patch_id);
// decrement patch ref count
// but do not demote forever patches
if (patch.refCount < 255)
patch.refCount--;
if (patch.refCount > 0)
return; // do no (yet) remove it
// remove it from the texture_lookup
bool ok = texture_lookup.erase(TextureHash(patch.width, patch.height, config::TA_TEXTURES_PER_ROW, &textures[patch.texture_id]));
assert(ok);
// decrement main patch used counter
const uint32_t main_patch_id = texture_id_2_main_patch_id(patch.texture_id);
MainPatch &main_patch = main_patches[main_patch_id];
assert(main_patch.used_count > 0);
main_patch.used_count--;
// merge main patch?
if (main_patch.used_count == 0) {
logDebug("[TextureAtlas], merging main patch #%d\n", main_patch_id);
// remove sibling patches
for (uint32_t id : main_patch.free_patches) {
const Patch &patch = patches.at_const(id);
const uint32_t bin = ((patch.width-1) * config::TA_MAIN_PATCH_SIZE) + (patch.height-1);
auto it = std::find(bins[bin].begin(), bins[bin].end(), id);
assert(it != bins[bin].end());
bins[bin].erase(it);
patches.remove(id);
}
main_patch.free_patches.clear();
// add main patch (reuse this patch)
patch.texture_id = main_patch_id;
patch.width = config::TA_MAIN_PATCH_SIZE;
patch.height = config::TA_MAIN_PATCH_SIZE;
main_patch.free_patches.push_back(_patch_id);
// add to bins
bins[(config::TA_MAIN_PATCH_SIZE * config::TA_MAIN_PATCH_SIZE) - 1].push_back(_patch_id);
return;
}
// otherwise, make patch available again
// add to bins
const uint32_t bin = ((patch.width-1) * config::TA_MAIN_PATCH_SIZE) + (patch.height-1);
bins[bin].push_back(_patch_id);
// add to main patch
main_patch.free_patches.push_back(_patch_id);
}
bool wasModified(uint32_t& _out_offset, uint32_t& _out_num_elements)
{
return texels.wasModified(_out_offset, _out_num_elements);
}
void clearModified()
{
texels.clearModified();
}
Texel* data()
{
return texels.data();
}
};

1484
src/factory.cpp Normal file

File diff suppressed because it is too large Load Diff

27
src/factory.h Normal file
View File

@@ -0,0 +1,27 @@
#pragma once
#include "space_math.h"
#include "graphics.h"
#include "world.h"
namespace factory
{
// Run the amazing block welding algorithm
// Returns true on success, false otherwise
bool weld_block(BlockModel& _model);
// Returns pointer to generated data
// Data array is always 24 elements long
// Attention: Data ist only valid until next call
GPULineVertex *generate_AABB_outline(const AABB &_aabb);
// Returns pointer to generated data
// Attention: Data ist only valid until next call
uint32_t *generate_debug_full_block_1_model(); // 36 elements
uint32_t *generate_debug_full_block_2_model(); // 36 elements
uint32_t *generate_debug_full_block_3_model(); // 36 elements
uint32_t *generate_debug_pyramid_block_model(); // 18 elements
void generate_block_outline();
}

448
src/gameloop.cpp Normal file
View File

@@ -0,0 +1,448 @@
#include "gameloop.h"
#include <GLFW/glfw3.h>
#include <cfloat>
#include "graphics.h"
#include "world.h"
#include "renderer.h"
#include "util.h"
#include "factory.h"
#include "lib/camera.h"
#include "space_input.h"
#include "config.h"
std::atomic_bool game_shutdown_requested = false;
// std::atomic_flag frame_ready_flag = ATOMIC_FLAG_INIT; // frame was rendered - ready for new data
// std::atomic_flag input_ready_flag = ATOMIC_FLAG_INIT; // input was updated - new states can be read from the input system
// std::atomic_flag bgfx_ready_flag = ATOMIC_FLAG_INIT; // bgfx init is done
std::atomic_bool frame_ready_flag; // frame was rendered - ready for new data
std::atomic_bool input_ready_flag; // input was updated - new states can be read from the input system
std::atomic_bool bgfx_ready_flag; // bgfx init is done
SlotQueue<Event> event_queue(4);
uint64_t frameCounter = 0;
uint32_t resetFlags = BGFX_RESET_VSYNC;
Camera camera;
extern uint32_t window_width; // defined in main.cpp
extern uint32_t window_height; // we only read the value here
void init(bgfx::PlatformData *_platformData)
{
// Initialize bgfx using the native window handle
bgfx::Init init;
init.type = bgfx::RendererType::Vulkan;
init.platformData = *_platformData;
init.resolution.width = window_width;
init.resolution.height = window_height;
init.resolution.reset = resetFlags;
if (!bgfx::init(init))
DIE("Could not initialize bgfx\n");
// Signal main thread that initialization is done
// bgfx_ready_flag.test_and_set();
bgfx_ready_flag = true;
bgfx::setViewClear(0, BGFX_CLEAR_COLOR | BGFX_CLEAR_DEPTH, 0x443355FF);
bgfx::setViewRect(0, 0, 0, bgfx::BackbufferRatio::Equal);
// Render initial frame#0
flag_wait(&input_ready_flag);
bgfx::touch(0);
bgfx::frame();
// Perform startup checks and abort if necessary
startup_checks();
// Initialize systems
gfx::init();
world::init();
// Camera
camera = camera_init();
camera.target_position = {0, 1, -10};
camera.mode = CAMERA_MODE_FIRST_PERSON;
camera.minPitch = -bx::kPiHalf;
camera.maxPitch = bx::kPiHalf;
}
void debug_populate_world()
{
// Add some debug things to the world
// add models
// BlockModel (Single Debug Component)
uint32_t m0, m1, m2, m3;
m0 = world::add_block_model();
uint32_t comps[512] = {};
comps[0] = 24; // debug component cube
world::set_block_model_components(m0, comps);
m1 = world::add_block_model();
bx::memSet(comps, 0x0, 512*sizeof(int32_t));
comps[1] = 24; // debug component cube
comps[2] = 24; // debug component cube
world::set_block_model_components(m1, comps);
// TODO add different models
m2 = m3 = m0;
// add grids
uint32_t g0, g1;
g0 = world::add_grid(Vec3(0.0f), Quat::unit());
// Vec3 g1_pos = Vec3(-5.0f, -5.0f, -5.0f);
Vec3 g1_pos = Vec3(0.0f, -0.5f, -7.0f);
// g1 = world::add_grid(g1_pos, bx::fromEuler(Vec3(bx::kPiQuarter)));
g1 = world::add_grid(g1_pos, Quat::unit());
// add chunks
uint32_t c0, c1, c2, c3;
c0 = world::add_chunk(g0, 0, 0, 0);
c1 = world::add_chunk(g0, 1, 0, 0);
c2 = world::add_chunk(g1, 0, 0, 0);
c3 = world::add_chunk(g0, 1, 1, 0);
// add a few blocks
uint32_t b0, b1, b2, b3, b4;
b0 = world::add_block(c0, block_transform_pack(7, 2, 1, 0), m0, m0);
b1 = world::add_block(c1, block_transform_pack(0, 2, 1, 0), m1, m1);
b2 = world::add_block(c2, block_transform_pack(0, 0, 0, 0), m1, m1);
b3 = world::add_block(c2, block_transform_pack(1, 0, 0, 0), m2, m2);
b4 = world::add_block(c0, block_transform_pack(0, 0, 0, 0), m2, m2);
b4 = world::add_block(c0, block_transform_pack(0, 2, 0, 0), m1, m1);
b4 = world::add_block(c0, block_transform_pack(0, 4, 0, 0), m1, m1);
b4 = world::add_block(c0, block_transform_pack(0, 6, 0, 0), m1, m1);
b4 = world::add_block(c0, block_transform_pack(0, 7, 0, 0), m2, m2);
b4 = world::add_block(c0, block_transform_pack(0, 0, 7, 0), m2, m2);
b4 = world::add_block(c0, block_transform_pack(7, 0, 0, 0), m2, m3);
// setup view into world
camera_look_at(&camera, bx::sub(Vec3(0.0f), camera_eye(&camera)), Vec3(0.0f, 1.0f, 0.0f));
}
bool debug_animate = true;
void debug_animate_world()
{
if (debug_animate)
{
const float increment = bx::toRad(0.2f);
for (uint32_t grid_id : world::get_grid_list())
{
if (grid_id == 2)
{
const Grid &grid = world::get_grid(grid_id);
Vec3 pos = grid.position;
pos.x = pos.x * bx::cos(increment) - pos.z * bx::sin(increment);
pos.z = pos.x * bx::sin(increment) + pos.z * bx::cos(increment);
world::update_grid_position(grid_id, pos);
return;
}
}
debug_animate = false;
}
}
void handle_event_queue()
{
// Handle main thread events
for (Event *ev = event_queue.begin_pop(); ev != NULL; event_queue.end_pop(), ev = event_queue.begin_pop())
{
switch (ev->type)
{
case Resize:
bgfx::reset(window_width, window_height, resetFlags);
bgfx::setViewRect(0, 0, 0, bgfx::BackbufferRatio::Equal);
break;
default:
logWarn("main: Encountered unknown event type => ignoring\n");
break;
}
}
}
int32_t runGameloopThread(bx::Thread *_thread_self, void *_platformData)
{
using input::Axis;
using input::Button;
init((bgfx::PlatformData *)_platformData);
debug_populate_world();
uint16_t num_block_selection = 0;
while (!game_shutdown_requested)
{
flag_wait(&input_ready_flag); // wait for input updates to be written to input system
// input::detect_input();
camera_move(&camera, Vec3(
input::axis_value_is(Axis::MoveForward),
input::axis_value_is(Axis::MoveUp),
input::axis_value_is(Axis::MoveRight)));
camera_rotate(&camera, Vec3(
input::axis_value_is(Axis::CameraPitch),
input::axis_value_is(Axis::CameraYaw),
0.0f));
if (input::button_pressed(Button::LookAtOrigin))
{
const Vec3 dir = cm_normalizeVec3(cm_negate(camera_eye(&camera)));
camera_look_at(&camera, dir, CAMERA_WORLD_UP);
}
game_shutdown_requested = input::button_released(Button::GameShouldExit);
// At this point we want to make sure that the frame actually got rendered, before we update world data.
// So we wait for the rendering thread to signal this.
// TODO use bgfx::update(, releaseFunction) to determine when updating of individual buffer is allowed
// instead of this global flag. Maybe even allow per buffer granularity.
flag_wait(&frame_ready_flag); // wait for last frame to be rendered before preparing new one (aka writing to buffers)
// logTrace(mfc, "API-THREAD: frame #%d\n", frameCounter);
// Handle inputs etc.
handle_event_queue();
bgfx::dbgTextClear();
// This dummy draw call is here to make sure that view 0 is cleared if no other draw calls are submitted to view 0.
bgfx::touch(0);
// Enable stats or debug text.
bgfx::setDebug(input::button_is_down(Button::ShowStats) ? BGFX_DEBUG_STATS : BGFX_DEBUG_TEXT);
float view[16];
camera_view_matrix(&camera, view);
float proj[16];
bx::mtxProj(proj, 60.0f, float(window_width) / float(window_height), 0.1f, 100.0f, bgfx::getCaps()->homogeneousDepth);
bgfx::setViewTransform(0, view, proj);
debug_animate_world(); // Move things around
// Update Game State
world::update();
num_block_selection = gfx::get_num_block_selection(); // Note: gfx::update() clears this to 0
gfx::update(); // upload new data onto GPU
// generate block instance matrices
renderer::dispatch_cubes_cs(camera_eye(&camera), num_block_selection);
// draw the prepared indirect buffer
renderer::draw_cubes(num_block_selection);
// Block picking
Vec4 red(1.0, 0.0, 0.0, 1.0);
Vec4 green(0.0, 1.0, 0.0, 1.0);
Ray ray = {
.position = camera_eye(&camera),
.direction = camera_forward(&camera)};
// InvRay inv_ray = InverseRay(ray);
float grid_mtx[16];
float chunk_mtx[16];
float block_mtx[16];
float inverse_mtx[16];
// iterate all grids
// TODO optimize with manual ray position offset instead of inv_matrices
// maybe even sorted testing? (a grid further away than the closest block is irrelevant?)
// maybe algorithmic solution? walk ray through 3D space and query specific chunks/blocks?
const std::vector<uint32_t> &grids = world::get_grid_list();
float grid_distance = FLT_MAX, chunk_distance = FLT_MAX, block_distance = FLT_MAX;
float closest_grid_distance = FLT_MAX, closest_chunk_distance = FLT_MAX, closest_block_distance = FLT_MAX;
uint32_t closest_grid_id = 0, closest_chunk_id = 0, closest_block_id = 0;
for (uint32_t grid_id : grids)
{
const Grid &grid = world::get_grid(grid_id);
world::get_grid_transform_mtx(grid_id, grid_mtx);
bx::mtxInverse(inverse_mtx, grid_mtx);
if (CheckCollisionRayOrientedBox(ray, grid.aabb, inverse_mtx, grid_distance))
{ // hit grid aabb => check its chunks
if (closest_chunk_id == 0 && grid_distance < closest_grid_distance)
{
closest_grid_distance = grid_distance;
closest_grid_id = grid_id;
}
for (uint32_t chunk_id : grid.chunk_id_list)
{
world::get_chunk_transform_mtx(chunk_id, chunk_mtx);
bx::mtxInverse(inverse_mtx, chunk_mtx);
if (CheckCollisionRayOrientedBox(ray, CHUNK_AABB, inverse_mtx, chunk_distance))
{ // hit chunk aabb => check its blocks
if (closest_block_id == 0 && chunk_distance < closest_chunk_distance)
{
closest_grid_distance = grid_distance;
closest_grid_id = grid_id;
closest_chunk_distance = chunk_distance;
closest_chunk_id = chunk_id;
}
const Chunk &chunk = world::get_chunk(chunk_id);
for (uint32_t block_id : chunk.block_ids)
{
world::get_block_transform_mtx(block_id, block_mtx);
bx::mtxInverse(inverse_mtx, block_mtx);
if (CheckCollisionRayOrientedBox(ray, BLOCK_AABB, inverse_mtx, block_distance) && block_distance < closest_block_distance)
{ // hit block aabb
closest_grid_distance = grid_distance;
closest_grid_id = grid_id;
closest_chunk_distance = chunk_distance;
closest_chunk_id = chunk_id;
closest_block_id = block_id;
closest_block_distance = block_distance;
// TODO perform geometry collision check aka ray-triangle-checks
}
}
}
}
}
}
// Draw outlines + info text
Vec3 eye = camera_eye(&camera);
Vec3 forward = camera_forward(&camera);
Vec3 point;
if (closest_grid_id > 0)
{
const Vec4 grid_color = Vec4(114.0f / 255.0f, 159.0f / 255.0f, 207.0f / 255.0f, 1.0f);
world::get_grid_transform_mtx(closest_grid_id, grid_mtx);
renderer::draw_lines(gfx::get_aabb_outline(world::get_grid(closest_grid_id).aabb), grid_color, grid_mtx);
point = bx::add(eye, bx::mul(forward, closest_grid_distance));
bgfx::dbgTextPrintf(0, 0, 0x09, "Grid @ %5.2f, %5.2f, %5.2f dist=%5.2f", point.x, point.y, point.z, closest_grid_distance);
}
if (closest_chunk_id > 0)
{
const Vec4 chunk_color = Vec4(52.0f / 255.0f, 226.0f / 255.0f, 226.0f / 255.0f, 1.0f);
world::get_chunk_transform_mtx(closest_chunk_id, chunk_mtx);
renderer::draw_lines(gfx::get_chunk_aabb_outline(), chunk_color, chunk_mtx);
point = bx::add(eye, bx::mul(forward, closest_chunk_distance));
bgfx::dbgTextPrintf(0, 1, 0x0B, "Chunk @ %5.2f, %5.2f, %5.2f dist=%5.2f", point.x, point.y, point.z, closest_chunk_distance);
}
if (closest_block_id > 0)
{
const Vec4 block_color = Vec4(196.0f / 255.0f, 160.0f / 255.0f, 0.0f, 1.0f);
world::get_block_transform_mtx(closest_block_id, block_mtx);
renderer::draw_lines(gfx::get_block_aabb_outline(), block_color, block_mtx);
point = bx::add(eye, bx::mul(forward, closest_block_distance));
bgfx::dbgTextPrintf(0, 2, 0x06, "Block @ %5.2f, %5.2f, %5.2f dist=%5.2f", point.x, point.y, point.z, closest_block_distance);
}
// Rotate Block
if (closest_block_id > 0 && input::button_pressed(Button::RotateBlockInc))
{
world::rotate_block(closest_block_id, true);
}
else if (closest_block_id > 0 && input::button_pressed(Button::RotateBlockDec))
{
world::rotate_block(closest_block_id, false);
}
// Remove Block
else if (closest_block_id > 0 && input::button_pressed(Button::RemoveBlock))
{ // there is a block in sight
world::remove_block(closest_block_id); // TODO maybe only stop from being selected for rendering and wait for server ack/refuse
// maybe remove from the world, but keep around? PS do not decrement model counter -> gpu unload? world puts it on sideline(just slotbuffer thing with linear search)
// Note: We do not remove chunks or grids. Not even when empty. The server will tell us when to.
}
// Place Block
else if (closest_block_id > 0)
{
// Calc new block direction
Vec3 hitpoint = bx::add(eye, bx::mul(forward, closest_block_distance));
Vec3 block_center = world::get_block_world_position(closest_block_id);
Vec3 dir = bx::sub(hitpoint, block_center);
dir = bx::mul(dir, 2.001f); // extend 0.5 coordinates to <1.0
int8_t x = (int8_t)dir.x; // These are now either -1, 0 or 1
int8_t y = (int8_t)dir.y; // We only allow one direction through
int8_t z = (int8_t)dir.z;
assert(x == -1 || x == 0 || x == 1);
assert(y == -1 || y == 0 || y == 1);
assert(z == -1 || z == 0 || z == 1);
if (x != 0)
{
y = z = 0;
}
if (y != 0)
{
x = z = 0;
}
if (z != 0)
{
x = y = 0;
}
// Get selected block and chunk coordinates in chunk
const Block &block = world::get_block(closest_block_id);
const Chunk &chunk = world::get_chunk(block.parent_ref);
uint16_t block_offset_x, block_offset_y, block_offset_z, block_orientation;
block_transform_unpack(block.transform, block_offset_x, block_offset_y, block_offset_z, block_orientation);
uint16_t chunk_offset_x = chunk.parent_offset_x << 3; // <<3 == *8
uint16_t chunk_offset_y = chunk.parent_offset_y << 3;
uint16_t chunk_offset_z = chunk.parent_offset_z << 3;
// Get new block offset
block_offset_x += x;
block_offset_y += y;
block_offset_z += z;
if (block_offset_x >= 0 && block_offset_x < 8 &&
block_offset_y >= 0 && block_offset_y < 8 &&
block_offset_z >= 0 && block_offset_z < 8)
{ // new block is still in same chunk
if (input::button_pressed(Button::PlaceBlock))
{ // place it
uint16_t new_block_transform = block_transform_pack(block_offset_x, block_offset_y, block_offset_z, 0);
if (chunk.block_ids[block_transform_to_chunk_index(new_block_transform)] == 0)
{ // no block yet
world::add_block(closest_chunk_id, block_transform_pack(block_offset_x, block_offset_y, block_offset_z, 0), 1, 1); // TODO do not hardcode block model
}
}
else
{ // render outline
const Vec4 block_color = Vec4(0.0f / 255.0f, 0.0f / 255.0f, 0.0f / 255.0f, 1.0f);
world::get_block_transform_mtx(block_offset_x, block_offset_y, block_offset_z, 0, block.parent_ref, block_mtx);
renderer::draw_lines(gfx::get_block_aabb_outline(), block_color, block_mtx);
}
} // TODO new block in neighboring chunk
}
// Crosshair
renderer::draw_crosshair(&camera);
// Advance to next frame. Main thread will be kicked to process submitted rendering primitives.
bgfx::frame();
frameCounter++;
if (config::DEBUG_EXIT_AFTER_FRAME > 0 && frameCounter > config::DEBUG_EXIT_AFTER_FRAME)
game_shutdown_requested = true;
}
// Release resources
gfx::destroy();
bgfx::shutdown();
return 0;
}

73
src/gameloop.h Normal file
View File

@@ -0,0 +1,73 @@
#pragma once
#include <bx/bx.h>
#include <bx/thread.h>
#include <cstdint>
#include <atomic>
#include "data/slot_queue.h"
typedef enum event_type
{
Exit, // Application should shutdown
Key, // Keyboard input
MouseButton, // Mouse buttons
MouseCursor, // Mouse movement input
MouseScroll, // Mouse scrolling
Resize, // Window resized
Joystick, // Joystick connected/disconnected
} EventType;
typedef struct event
{
EventType type;
// struct exit_event {};
// struct resize_event {};
struct key_event
{
int keycode;
int action;
};
struct mouse_button_event
{
int button;
int action;
};
struct cursor_event
{
float xpos;
float ypos;
};
struct scroll_event
{
float xoffset;
float yoffset;
};
struct joystick_event
{
int id;
int event;
};
union // purposeful anonymous union, as struct are named
{
struct key_event key;
struct mouse_button_event mouse_button;
struct cursor_event cursor;
struct scroll_event scroll;
struct joystick_event joystick;
};
} Event;
constexpr size_t event_size = sizeof(Event);
extern std::atomic_bool game_shutdown_requested;
// extern std::atomic_flag frame_ready_flag; // frame was rendered - ready for new data
// extern std::atomic_flag input_ready_flag; // input for next frame is ready
// extern std::atomic_flag bgfx_ready_flag; // bgfx init is done
extern std::atomic_bool frame_ready_flag; // frame was rendered - ready for new data
extern std::atomic_bool input_ready_flag; // input for next frame is ready
extern std::atomic_bool bgfx_ready_flag; // bgfx init is done
extern SlotQueue<Event> event_queue;
extern uint64_t frameCounter;
int32_t runGameloopThread(bx::Thread *_thread_self, void *_platformData /*userData*/);

453
src/graphics.cpp Normal file
View File

@@ -0,0 +1,453 @@
#include "graphics.h"
#include <vector>
#include <bx/bx.h>
#include "data/slot_buffer.h"
#include "data/first_fit_buffer.h"
#include "data/linear_buffer.h"
#include "config.h"
#include <cassert>
#include "factory.h"
#include "world.h"
#include "data/texture_atlas.h"
// Static variables
bgfx::VertexLayout GPUGrid::layout;
bgfx::VertexLayout GPUChunk::layout;
bgfx::VertexLayout GPUBlock::layout;
bgfx::VertexLayout GPUBlockSelection::layout;
bgfx::VertexLayout GPURenderInstance::layout;
bgfx::VertexLayout GPUDummyVertex::layout;
bgfx::VertexLayout GPULineVertex::layout;
namespace gfx
{
/* Graphics data */
// grids
SlotBuffer<GPUGrid> cpu_grids(config::INITIAL_NUM_GRIDS);
bgfx::DynamicVertexBufferHandle gpu_grids = BGFX_INVALID_HANDLE;
// chunks
SlotBuffer<GPUChunk> cpu_chunks(config::INITIAL_NUM_CHUNKS);
bgfx::DynamicVertexBufferHandle gpu_chunks = BGFX_INVALID_HANDLE;
// blocks (pre culling)
SlotBuffer<GPUBlock> cpu_blocks(config::INITIAL_NUM_BLOCKS);
bgfx::DynamicVertexBufferHandle gpu_blocks = BGFX_INVALID_HANDLE;
// block selection (post culling)
LinearBuffer<float> cpu_block_selection(config::INITIAL_BLOCK_SELECTION); // TODO should be uint32_t => use bgfx packing/conversion
bgfx::DynamicVertexBufferHandle gpu_block_selection = BGFX_INVALID_HANDLE;
// block models (index buffer with block render data)
FirstFitBuffer<uint32_t> cpu_block_models(config::INITIAL_BLOCK_MODEL_BUFFER_SIZE);
bgfx::DynamicIndexBufferHandle gpu_block_models = BGFX_INVALID_HANDLE;
// dummy vertex buffer
bgfx::VertexBufferHandle gpu_dummy_vertex_buffer = BGFX_INVALID_HANDLE;
// the indirect buffer holds the indirect draw calls generated in the compute shader
bgfx::IndirectBufferHandle gpu_indirect_buffer = BGFX_INVALID_HANDLE;
// per instance data (matrices) generated by compute shader
bgfx::DynamicVertexBufferHandle gpu_instance_buffer = BGFX_INVALID_HANDLE;
// line buffer
FirstFitBuffer<GPULineVertex> cpu_lines(config::INITIAL_LINE_BUFFER_SIZE);
bgfx::DynamicVertexBufferHandle gpu_line_buffer = BGFX_INVALID_HANDLE;
// uniforms
bgfx::UniformHandle texture_atlas_sampler = BGFX_INVALID_HANDLE;
bgfx::UniformHandle u_cubes_compute_params = BGFX_INVALID_HANDLE;
bgfx::UniformHandle u_line_color = BGFX_INVALID_HANDLE;
// shader
bgfx::ProgramHandle cubes_shader = BGFX_INVALID_HANDLE;
bgfx::ProgramHandle cubes_compute_shader = BGFX_INVALID_HANDLE;
bgfx::ProgramHandle lines_shader = BGFX_INVALID_HANDLE;
// textures
SlotBuffer<CompTexture> cpu_component_textures(config::INITIAL_NUM_COMPONENT_TEXTURES);
TextureAtlas cpu_texture_atlas = TextureAtlas();
bgfx::TextureHandle gpu_texture_atlas = BGFX_INVALID_HANDLE;
// lines
uint32_t aabb_outline = 0; // non static
uint32_t chunk_aabb_outline = 0;
uint32_t block_aabb_outline = 0;
/* Internal functions */
static bgfx::ShaderHandle loadShader(const char *filePath)
{
FILE *file = fopen(filePath, "rb");
if (!file)
DIE("loadShader: fopen(%s) failed\n", filePath);
fseek(file, 0, SEEK_END);
long fileSize = ftell(file);
fseek(file, 0, SEEK_SET);
const bgfx::Memory *mem = bgfx::alloc(fileSize + 1);
if (!mem)
DIE("loadShader: bgfx::alloc() failed\n");
fread(mem->data, 1, fileSize, file);
mem->data[mem->size - 1] = '\0';
fclose(file);
return bgfx::createShader(mem);
}
/* Public functions */
void init()
{
GPUGrid::init();
GPUChunk::init();
GPUBlock::init();
GPUBlockSelection::init();
GPURenderInstance::init();
GPUDummyVertex::init();
GPULineVertex::init();
// gpu buffer
gpu_grids = bgfx::createDynamicVertexBuffer(config::INITIAL_NUM_GRIDS, GPUGrid::layout, BGFX_BUFFER_COMPUTE_READ | BGFX_BUFFER_ALLOW_RESIZE);
gpu_chunks = bgfx::createDynamicVertexBuffer(config::INITIAL_NUM_CHUNKS, GPUChunk::layout, BGFX_BUFFER_COMPUTE_READ | BGFX_BUFFER_ALLOW_RESIZE);
gpu_blocks = bgfx::createDynamicVertexBuffer(config::INITIAL_NUM_BLOCKS, GPUBlock::layout, BGFX_BUFFER_COMPUTE_READ | BGFX_BUFFER_ALLOW_RESIZE);
gpu_block_selection = bgfx::createDynamicVertexBuffer(config::INITIAL_BLOCK_SELECTION, GPUBlockSelection::layout, BGFX_BUFFER_COMPUTE_READ | BGFX_BUFFER_ALLOW_RESIZE);
gpu_block_models = bgfx::createDynamicIndexBuffer(config::INITIAL_BLOCK_MODEL_BUFFER_SIZE, BGFX_BUFFER_INDEX32 | BGFX_BUFFER_ALLOW_RESIZE);
GPUDummyVertex dummy_vertex = {0};
gpu_dummy_vertex_buffer = bgfx::createVertexBuffer(bgfx::copy(&dummy_vertex, sizeof(float)), GPUDummyVertex::layout, BGFX_BUFFER_NONE);
gpu_indirect_buffer = bgfx::createIndirectBuffer(config::INITIAL_BLOCK_SELECTION);
gpu_instance_buffer = bgfx::createDynamicVertexBuffer(config::INITIAL_BLOCK_SELECTION, GPURenderInstance::layout, BGFX_BUFFER_COMPUTE_WRITE);
gpu_line_buffer = bgfx::createDynamicVertexBuffer(config::INITIAL_LINE_BUFFER_SIZE, GPULineVertex::layout, BGFX_BUFFER_ALLOW_RESIZE);
// uniforms
texture_atlas_sampler = bgfx::createUniform("texture_atlas_sampler", bgfx::UniformType::Sampler);
u_cubes_compute_params = bgfx::createUniform("u_cubes_compute_params", bgfx::UniformType::Vec4);
u_line_color = bgfx::createUniform("u_line_color", bgfx::UniformType::Vec4);
// shader
cubes_shader = bgfx::createProgram(
loadShader("./assets/shaders/vs_cubes.spv"),
loadShader("./assets/shaders/fs_cubes.spv"), true);
cubes_compute_shader = bgfx::createProgram(
loadShader("./assets/shaders/cs_cubes.spv"), true);
lines_shader = bgfx::createProgram(
loadShader("./assets/shaders/vs_lines.spv"),
loadShader("./assets/shaders/fs_lines.spv"), true);
// textures
cpu_texture_atlas.init();
gpu_texture_atlas = bgfx::createTexture2D(
config::TA_WIDTH * config::TA_MAIN_PATCH_SIZE * config::TA_TEXTURE_SIZE,
config::TA_HEIGHT * config::TA_MAIN_PATCH_SIZE * config::TA_TEXTURE_SIZE,
false, 1, bgfx::TextureFormat::RGBA8, BGFX_TEXTURE_NONE | BGFX_SAMPLER_POINT, NULL);
// cpu buffer index 0 is invalid for all purposes => we push an empty element
uint32_t ref;
cpu_grids.add(ref);
cpu_grids.clearModified();
assert(ref == 0);
cpu_chunks.add(ref);
cpu_chunks.clearModified();
assert(ref == 0);
cpu_blocks.add(ref);
cpu_blocks.clearModified();
assert(ref == 0);
ref = cpu_block_models.add(NULL, 0);
cpu_block_models.clearModified();
assert(ref == 0);
// Push debug texture into invalid index 0
const Texel O = {204, 131, 67, 255}; // Orange
const Texel W = {255, 255, 255, 255};
const Texel B = {0, 0, 0, 255};
Texel texture[16] = {
W, O, O, W,
O, W, W, O,
W, O, O, W,
O, W, W, O,
};
uint32_t debug_comp_tex_id = add_component_textures(texture);
assert(debug_comp_tex_id == 0);
// add a first debug texture to the atlas
// This also reserves the invalid texture_index 0
cpu_texture_atlas.add_patch(1, 1, &debug_comp_tex_id);
// load default AABB outlines
aabb_outline = add_line(factory::generate_AABB_outline(BLOCK_AABB), 24); // use block aabb initially
chunk_aabb_outline = add_line(factory::generate_AABB_outline(CHUNK_AABB), 24);
block_aabb_outline = add_line(factory::generate_AABB_outline(BLOCK_AABB), 24);
}
// Call every frame!
// Updates outstanding changes from CPU -> GPU
void update()
{
uint32_t offset = 0, num_elements = 0;
bool resized = false;
if (cpu_grids.wasModified(offset, num_elements))
{
bgfx::update(gpu_grids, offset, bgfx::makeRef(cpu_grids.data() + offset, sizeof(GPUGrid) * num_elements));
cpu_grids.clearModified();
}
if (cpu_chunks.wasModified(offset, num_elements))
{
bgfx::update(gpu_chunks, offset, bgfx::makeRef(cpu_chunks.data() + offset, sizeof(GPUChunk) * num_elements));
cpu_chunks.clearModified();
}
if (cpu_blocks.wasModified(offset, num_elements))
{
bgfx::update(gpu_blocks, offset, bgfx::makeRef(cpu_blocks.data() + offset, sizeof(GPUBlock) * num_elements));
cpu_blocks.clearModified();
}
if (cpu_block_selection.wasModified(offset, num_elements, resized))
{
bgfx::update(gpu_block_selection, offset, bgfx::makeRef(cpu_block_selection.data() + offset, sizeof(float) * num_elements));
cpu_block_selection.clear();
if (resized)
{ // This means our indirect and instance buffers need to be resized too
bgfx::destroy(gpu_indirect_buffer);
gpu_indirect_buffer = bgfx::createIndirectBuffer(num_elements);
bgfx::destroy(gpu_instance_buffer);
gpu_instance_buffer = bgfx::createDynamicVertexBuffer(num_elements, GPURenderInstance::layout, BGFX_BUFFER_COMPUTE_WRITE);
}
}
if (cpu_block_models.wasModified(offset, num_elements))
{
bgfx::update(gpu_block_models, offset, bgfx::makeRef(cpu_block_models.data() + offset, sizeof(uint32_t) * num_elements));
cpu_block_models.clearModified();
}
if (cpu_lines.wasModified(offset, num_elements))
{
bgfx::update(gpu_line_buffer, offset, bgfx::makeRef(cpu_lines.data() + offset, sizeof(GPULineVertex) * num_elements));
cpu_lines.clearModified();
}
if (cpu_texture_atlas.wasModified(offset, num_elements))
{
// offset == texel_id
// we need to update entire rows, since the texel data is linear
const uint16_t offset_y_begin = offset / config::TA_TEXELS_PER_ROW;
const uint16_t offset_y_end = ((offset + num_elements) / config::TA_TEXELS_PER_ROW) + 1;
const uint16_t num_rows = offset_y_end + offset_y_begin;
bgfx::updateTexture2D(gpu_texture_atlas, 0, 0, 0, offset_y_begin, config::TA_TEXELS_PER_ROW, num_rows,
bgfx::makeRef(cpu_texture_atlas.data() + (offset_y_begin * config::TA_TEXELS_PER_ROW), num_rows * config::TA_TEXELS_PER_ROW * sizeof(Texel)));
cpu_texture_atlas.clearModified();
}
}
void destroy()
{
bgfx::destroy(gpu_grids);
gpu_grids = BGFX_INVALID_HANDLE;
bgfx::destroy(gpu_chunks);
gpu_chunks = BGFX_INVALID_HANDLE;
bgfx::destroy(gpu_blocks);
gpu_blocks = BGFX_INVALID_HANDLE;
bgfx::destroy(gpu_block_selection);
gpu_block_selection = BGFX_INVALID_HANDLE;
bgfx::destroy(gpu_block_models);
gpu_block_models = BGFX_INVALID_HANDLE;
bgfx::destroy(gpu_dummy_vertex_buffer);
gpu_dummy_vertex_buffer = BGFX_INVALID_HANDLE;
bgfx::destroy(gpu_indirect_buffer);
gpu_indirect_buffer = BGFX_INVALID_HANDLE;
bgfx::destroy(gpu_instance_buffer);
gpu_instance_buffer = BGFX_INVALID_HANDLE;
bgfx::destroy(gpu_line_buffer);
gpu_line_buffer = BGFX_INVALID_HANDLE;
bgfx::destroy(texture_atlas_sampler);
texture_atlas_sampler = BGFX_INVALID_HANDLE;
bgfx::destroy(u_cubes_compute_params);
u_cubes_compute_params = BGFX_INVALID_HANDLE;
bgfx::destroy(u_line_color);
u_line_color = BGFX_INVALID_HANDLE;
bgfx::destroy(cubes_shader);
cubes_shader = BGFX_INVALID_HANDLE;
bgfx::destroy(cubes_compute_shader);
cubes_compute_shader = BGFX_INVALID_HANDLE;
bgfx::destroy(lines_shader);
lines_shader = BGFX_INVALID_HANDLE;
bgfx::destroy(gpu_texture_atlas);
gpu_texture_atlas = BGFX_INVALID_HANDLE;
}
GPUGrid &add_grid(uint32_t &_out_ref)
{
return cpu_grids.add(_out_ref);
}
GPUGrid &update_grid(const uint32_t _ref)
{
return cpu_grids.at(_ref);
}
void remove_grid(const uint32_t _ref)
{
cpu_grids.remove(_ref);
}
GPUChunk &add_chunk(uint32_t &_out_ref)
{
return cpu_chunks.add(_out_ref);
}
void remove_chunk(const uint32_t _ref)
{
cpu_chunks.remove(_ref);
}
GPUBlock &add_block(uint32_t &_out_ref)
{
return cpu_blocks.add(_out_ref);
}
GPUBlock &update_block(const uint32_t _ref)
{
return cpu_blocks.at(_ref);
}
void remove_block(const uint32_t _ref)
{
cpu_blocks.remove(_ref);
}
void add_block_selection(const uint32_t _ref)
{
cpu_block_selection.add((float)_ref);
}
uint16_t get_num_block_selection()
{
return cpu_block_selection.get_num_elements();
}
uint32_t add_block_model(const uint32_t *_data, const uint32_t _num_elements)
{
return cpu_block_models.add(_data, _num_elements);
}
void remove_block_model(const uint32_t _ref)
{
cpu_block_models.remove(_ref);
}
void get_block_model_offset(const uint32_t _ref, uint32_t &_out_offset, uint32_t &_out_num_elements)
{
cpu_block_models.get_offset(_ref, _out_offset, _out_num_elements);
}
// A line always consists of 2 vertices.
uint32_t add_line(const GPULineVertex *_data, const uint32_t _num_elements)
{
assert(_num_elements % 2 == 0);
return cpu_lines.add(_data, _num_elements);
}
uint32_t update_line(const uint32_t _ref, const GPULineVertex *_data)
{
return cpu_lines.update(_ref, _data);
}
void remove_line(const uint32_t _ref)
{
cpu_lines.remove(_ref);
}
void get_line_offset(const uint32_t _ref, uint32_t &_out_offset, uint32_t &_out_num_elements)
{
cpu_lines.get_offset(_ref, _out_offset, _out_num_elements);
}
/////////////////////////////////////////////////////////////////
uint32_t add_component_textures(const Texel *_textures)
{
uint32_t ref;
CompTexture& tex = cpu_component_textures.add(ref);
bx::memCopy(tex.data, _textures, config::TA_TEXELS_PER_TEXTURE * sizeof(Texel));
return ref;
}
void remove_component_textures(uint32_t _ref)
{
cpu_component_textures.remove(_ref);
}
const Texel *get_component_texture(const uint32_t _ref)
{
return cpu_component_textures.at_const(_ref).data;
}
/////////////////////////////////////////////////////////////////
uint32_t add_patch(const uint8_t _width, const uint8_t _height, const uint32_t *_component_texture_ids)
{
return cpu_texture_atlas.add_patch(_width, _height, _component_texture_ids);
}
void remove_patch(const uint8_t _patch_id)
{
cpu_texture_atlas.remove_patch(_patch_id);
}
void get_patch_texture_ids(const uint8_t _patch_id, uint32_t *_out_texture_ids)
{
cpu_texture_atlas.get_patch_texture_ids(_patch_id, _out_texture_ids);
}
/////////////////////////////////////////////////////////////////
bgfx::DynamicVertexBufferHandle get_grid_buffer() { return gpu_grids; }
bgfx::DynamicVertexBufferHandle get_chunk_buffer() { return gpu_chunks; }
bgfx::DynamicVertexBufferHandle get_block_buffer() { return gpu_blocks; }
bgfx::DynamicVertexBufferHandle get_block_selection_buffer() { return gpu_block_selection; }
bgfx::DynamicIndexBufferHandle get_block_model_buffer() { return gpu_block_models; }
bgfx::VertexBufferHandle get_dummy_vertex_buffer() { return gpu_dummy_vertex_buffer; }
bgfx::IndirectBufferHandle get_indirect_buffer() { return gpu_indirect_buffer; }
bgfx::DynamicVertexBufferHandle get_instance_buffer() { return gpu_instance_buffer; }
bgfx::DynamicVertexBufferHandle get_line_buffer() { return gpu_line_buffer; }
bgfx::UniformHandle get_uniform_texture_atlas_sampler() { return texture_atlas_sampler; }
bgfx::UniformHandle get_uniform_cubes_compute_params() { return u_cubes_compute_params; }
bgfx::UniformHandle get_uniform_line_color() { return u_line_color; }
bgfx::ProgramHandle get_cubes_shader() { return cubes_shader; }
bgfx::ProgramHandle get_cubes_compute_shader() { return cubes_compute_shader; }
bgfx::ProgramHandle get_lines_shader() { return lines_shader; }
bgfx::TextureHandle get_texture_atlas() { return gpu_texture_atlas; }
uint32_t get_aabb_outline(const AABB &_aabb)
{
// Note: update is possible since, an aabb outline always has 24 elements
return update_line(aabb_outline, factory::generate_AABB_outline(_aabb));
}
uint32_t get_last_aabb_outline() { return aabb_outline; }
uint32_t get_chunk_aabb_outline() { return chunk_aabb_outline; }
uint32_t get_block_aabb_outline() { return block_aabb_outline; }
}

239
src/graphics.h Normal file
View File

@@ -0,0 +1,239 @@
#pragma once
#include <bgfx/bgfx.h>
#include "space_math.h"
/* * * * * * * * * * * * * * * * * * * * * * * */
/* The gfx namespace manages all GPU resources */
/* * * * * * * * * * * * * * * * * * * * * * * */
// ------------ \\
// DATA STRUCTS \\
// ------------ \\
// Per grid data for rendering
typedef struct gpu_grid
{
float m_mtx[16];
static void init()
{
layout
.begin()
.add(bgfx::Attrib::TexCoord0, 4, bgfx::AttribType::Float)
.add(bgfx::Attrib::TexCoord1, 4, bgfx::AttribType::Float)
.add(bgfx::Attrib::TexCoord2, 4, bgfx::AttribType::Float)
.add(bgfx::Attrib::TexCoord3, 4, bgfx::AttribType::Float)
.end();
};
static bgfx::VertexLayout layout;
} GPUGrid;
// Per chunk data for rendering
typedef struct gpu_chunk
{
float grid_id;
float grid_offset_x;
float grid_offset_y;
float grid_offset_z;
static void init()
{
layout
.begin()
.add(bgfx::Attrib::TexCoord0, 4, bgfx::AttribType::Float)
.end();
};
static bgfx::VertexLayout layout;
} GPUChunk;
// Per block data for rendering (all blocks ready for rendering aka in render distance)
// This points to a block_blueprint in the index buffer
// It represents a single block instance (pre culling)
typedef struct gpu_block
{
float chunk_id;
float transform; // position in chunk and rotation
float index_buf_offset;
float num_indices;
static void init()
{
layout
.begin()
.add(bgfx::Attrib::TexCoord0, 4, bgfx::AttribType::Float)
.end();
};
static bgfx::VertexLayout layout;
} GPUBlock;
// Helper class
struct CompTexture
{
Texel data[4*4];
};
// Post culling selection
// This is referencing a GPUBlock
typedef struct gpu_block_selection
{
float index;
static void init()
{
layout
.begin()
.add(bgfx::Attrib::TexCoord0, 1, bgfx::AttribType::Float)
.end();
};
static bgfx::VertexLayout layout;
} GPUBlockSelection;
// Per instance data, generated by computer shader
// Required for indirect drawing
// This is just the blocks world matrix (position+orientation in the world)
typedef struct gpu_render_instance
{
float m_mtx[16];
static void init()
{
layout
.begin()
.add(bgfx::Attrib::TexCoord0, 4, bgfx::AttribType::Float)
.add(bgfx::Attrib::TexCoord1, 4, bgfx::AttribType::Float)
.add(bgfx::Attrib::TexCoord2, 4, bgfx::AttribType::Float)
.add(bgfx::Attrib::TexCoord3, 4, bgfx::AttribType::Float)
.end();
};
static bgfx::VertexLayout layout;
} GPURenderInstance;
// Dummy vertex
typedef struct gpu_dummy_vertex
{
float data;
static void init()
{
layout
.begin()
.add(bgfx::Attrib::TexCoord0, 1, bgfx::AttribType::Float)
.end();
};
static bgfx::VertexLayout layout;
} GPUDummyVertex;
// A line between two points
typedef struct gpu_line_vertex
{
Vec3 position;
static void init()
{
layout
.begin()
.add(bgfx::Attrib::TexCoord0, 3, bgfx::AttribType::Float)
.end();
};
static bgfx::VertexLayout layout;
} GPULineVertex;
namespace gfx
{
// --------- \\
// FUNCTIONS \\
// --------- \\
void init();
void update();
void destroy();
// Adds grid data for the gpu
// Returns reference for updating/removing
GPUGrid &add_grid(uint32_t &_out_ref);
GPUGrid &update_grid(const uint32_t _ref);
void remove_grid(const uint32_t _ref);
// Adds chunk data for the gpu
// Returns reference for updating/removing
GPUChunk &add_chunk(uint32_t &_out_ref);
void remove_chunk(const uint32_t _ref);
// Adds block data for the gpu
// Returns reference for updating/removing
GPUBlock &add_block(uint32_t &_out_ref);
GPUBlock &update_block(const uint32_t _ref);
void remove_block(const uint32_t _ref);
void add_block_selection(const uint32_t _ref);
uint16_t get_num_block_selection();
// Adds block model data (index buffer) for the gpu
// Returns reference for updating/removing
uint32_t add_block_model(const uint32_t *_data, const uint32_t _num_elements);
void remove_block_model(const uint32_t _ref);
void get_block_model_offset(const uint32_t _ref, uint32_t &_out_offset, uint32_t &_out_num_elements);
// Adds line data (line segments) for the gpu
// Returns reference for updating/removing
uint32_t add_line(const GPULineVertex *_data, const uint32_t _num_elements);
// Attention: new _data MUST have the exact same _num_elements as the original
uint32_t update_line(const uint32_t _ref, const GPULineVertex *_data);
void remove_line(const uint32_t _ref);
void get_line_offset(const uint32_t _ref, uint32_t &_out_offset, uint32_t &_out_num_elements);
// Adds 8*8 texels to the internal buffer
// Returns id to the textures
uint32_t add_component_textures(const Color* textures);
void remove_component_textures(uint32_t _ref);
const Texel* get_component_texture(uint32_t _ref);
// Adds a patch to the atlas with size _width*_height
// Deduplicates the _component_texture_ids if possible, otherwise copies the data
uint32_t add_patch(const uint8_t _width, const uint8_t _height, const uint32_t* _component_texture_ids);
// Copies _data into the given patch
// Returns texture id for vertex buffer generation
// uint32_t update_patch_texture(const uint32_t _patch_id, const uint8_t _offset_x, const uint8_t _offset_y, const uint32_t _component_texture_id);
void remove_patch(const uint8_t _patch_id);
// Returns 4 texture ids (for vertex buffer generation) of given patch
void get_patch_texture_ids(const uint8_t _patch_id, uint32_t* _out_texture_ids);
bgfx::DynamicVertexBufferHandle get_grid_buffer();
bgfx::DynamicVertexBufferHandle get_chunk_buffer();
bgfx::DynamicVertexBufferHandle get_block_buffer();
bgfx::DynamicVertexBufferHandle get_block_selection_buffer();
bgfx::DynamicIndexBufferHandle get_block_model_buffer();
bgfx::VertexBufferHandle get_dummy_vertex_buffer();
bgfx::IndirectBufferHandle get_indirect_buffer();
bgfx::DynamicVertexBufferHandle get_instance_buffer();
bgfx::DynamicVertexBufferHandle get_line_buffer();
bgfx::UniformHandle get_uniform_texture_atlas_sampler();
bgfx::UniformHandle get_uniform_cubes_compute_params();
bgfx::UniformHandle get_uniform_line_color();
bgfx::ProgramHandle get_cubes_shader();
bgfx::ProgramHandle get_cubes_compute_shader();
bgfx::ProgramHandle get_lines_shader();
bgfx::TextureHandle get_texture_atlas();
uint32_t get_aabb_outline(const AABB &_aabb);
// Returns last aabb outline generated by "get_aabb_outline(const AABB& _aabb)"
uint32_t get_last_aabb_outline();
uint32_t get_chunk_aabb_outline();
uint32_t get_block_aabb_outline();
} // namespace gfx

4
src/lib/camera.cpp Normal file
View File

@@ -0,0 +1,4 @@
#define CAMERA_IMPLEMENTATION
#include "camera.h"
/// This file only exists as a translation unit for the library implementation

446
src/lib/camera.h Normal file
View File

@@ -0,0 +1,446 @@
/*
* INFO:
*
* C/C++ (single) header quaternion based 3D camera system
* for games and other 3D graphics applications.
*
*
* FEATURES:
*
* - Quaternion based
* This naturally avoids gimbal lock and enables smooth interpolation (ex. for cinematic camera movement)
* - Precise manipulation
* A call of 'camera_rotate(&camera, {45 * DEG_TO_RAD, 0, 0});' will rotate exactly 45 degrees.
* - Engine agnostic
* No matter your input system, the camera is updated in angles
* - Different camera modes to control the cameras behaviour
* - Allows custom configuration
* - Can be changed at runtime
* - See CAMERA_MODE_* defines for more
* - Pre-defined modes: CAMERA_MODE_FREE, CAMERA_MODE_FIRST_PERSON, CAMERA_MODE_THIRD_PERSON, CAMERA_MODE_ORBITAL
* - Supports seamless camera mode transitions (ex. first person -> third person)
* - Full access to all camera state data - nothing is hidden
* You can access and manipulate the entire camera state at any time!
* - Supports angle clamping
* Restrict the angles your camera is allowed to work in
*
*
* USAGE:
*
* Simply add 'camera.h' and one 'camera_math.h' to your project and '#include "camera.h"' it wherever.
*
* ONE (and only ONE) source file must hold the implementation
* by using '#define CAMERA_IMPLEMENTATION' before including it with '#include "camera.h"'.
*
* Camera code is math heavy. For all required math functions we provide a separate 'camera_math.h' file.
* To get started you can use the 'camera_math_default.h'.
* Simply add it to your project and rename it to 'camera_math.h'.
*
* But your engine probably brings its own math library.
* In order to reduce the duplication of math-code you can create your own
* 'camera_math.h' implementing the required functions with the math library of your choice.
* This also allows you to pass and receive arguments without the need to convert them.
* See 'camera_math_bx.h' as an example.
*
*
* ANGLE CLAMPING:
*
* If angle clamping is activated, the corresponding limits must be set in the camera struct.
* All limits are expected in radians and min* must be smaller than max*.
* They are expected in the range [-pi; pi], with 0 representing no rotation.
* The camera rotations are restricting in WORLD space.
* This means if pitch AND yaw are clamped, this essentially creates a "window" the camera is not allowed to rotate out of.
*
* Example for clamping pitch:
* 1. Activate pitch clamping: 'camera.mode |= CAMERA_MODE_CLAMP_PITCH;'
* 2. Set limits: 'camera.maxPitch = -pi / 2.0f; // aka restrict to 90 deg upwards'
* 'camera.minPitch = pi / 2.0f; // aka restrict to 90 deg downwards'
*
*
* GENERAL NOTES:
*
* ALL camera struct members can be safely manipulated at any time.
*
* To change to a right-handed coordinate system simply change CAMERA_WORLD_FORWARD to (0.0f, 0.0f, -1.0f)
*
* CameraQuaternions are well suited to represent 3D orientation, but they do not accumulate
* many rotations in differing axes well and thus require occasional re-normalization.
* To accommodate for this and for general performance reasons changes are accumulated and the
* orientation quaternion is only updated when the view matrix is requested (once per frame).
*
* Query functions only return the correct value AFTER pending changes have been applied. (i.e. calling camera_view_matrix(..))
* Example:
* 1. camera_move(..) // Changes NOT yet applied
* 2. camera_eye_position(..) // Returned state does NOT yet include previous move
* 3. camera_view_matrix(..) // Changes are now applied
* 4. camera_eye_position(..) // Returned state DOES now include previous move
*
*
* LICENSE:
*
* MIT License
*
* Copyright (c) 2022 Crydsch Cube
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef CAMERA_HEADER_GUARD
#define CAMERA_HEADER_GUARD
#include <stdint.h>
#include "camera_math_bx.h"
/* World and mode defines */
#define CAMERA_WORLD_FORWARD CameraVec3(0.0f, 0.0f, 1.0f)
#define CAMERA_WORLD_UP CameraVec3(0.0f, 1.0f, 0.0f)
#define CAMERA_WORLD_RIGHT CameraVec3(1.0f, 0.0f, 0.0f)
// Camera mode configuration flags
// Can be combined with bitwise OR
#define CAMERA_MODE_DISABLE_ROLL UINT32_C(0x00000001) // Disables the roll axis
#define CAMERA_MODE_MOVE_IN_WORLDPLANE UINT32_C(0x00000002) // Projects movement onto world plane
#define CAMERA_MODE_CLAMP_PITCH_ANGLE UINT32_C(0x00000004) // Limits the pitch angle. Typically used in first/third person to prevent overrotation (i.e. somersaults).
#define CAMERA_MODE_CLAMP_YAW_ANGLE UINT32_C(0x00000008) // Limits the yaw angle.
#define CAMERA_MODE_CLAMP_ROLL_ANGLE UINT32_C(0x00000010) // Limits the roll angle.
// Free float camera mode (no restrictions applied)
#define CAMERA_MODE_FREE (0)
// First person camera mode
// Note: Set camera.minPitch = -pi/2 and camera.maxPitch = pi/2
#define CAMERA_MODE_FIRST_PERSON (0 \
| CAMERA_MODE_DISABLE_ROLL \
| CAMERA_MODE_MOVE_IN_WORLDPLANE \
| CAMERA_MODE_CLAMP_PITCH_ANGLE \
)
// Third person camera mode
// Note: Set camera.minPitch = -pi/2 and camera.maxPitch = pi/2
// Note: Use a target_distance > 0
#define CAMERA_MODE_THIRD_PERSON CAMERA_MODE_FIRST_PERSON
// Orbital camera mode (orbit around some target)
// Useful for inspecting models.
// Note: Set camera.minPitch = -pi/2 and camera.maxPitch = pi/2
#define CAMERA_MODE_ORBITAL (0 \
| CAMERA_MODE_DISABLE_ROLL \
| CAMERA_MODE_CLAMP_PITCH_ANGLE \
)
/* Camera struct */
typedef struct camera {
CameraVec3 target_position; // The target point, the camera is looking at. Aka camera eye position if camera.target_distance == 0.
float target_distance; // Camera distance from eye to target. Note: negative values create zoom-like behaviour.
CameraQuat orientation; // Camera rotation in 3D.
uint32_t mode; // Controls camera behaviour. See CAMERA_MODE_* defines.
// Temporary accumulator. Cleared on camera_view_matrix(..).
CameraVec3 movement_accumulator;
CameraVec3 rotation_accumulator;
// Angle clamping limits. See "Angle Clamping" for further information.
float minPitch;
float maxPitch;
float minYaw;
float maxYaw;
float minRoll;
float maxRoll;
} Camera;
/* Function declarations */
// Initialize/Reset the camera struct.
extern Camera camera_init();
// Returns the cameras current forward direction (normalized)
extern CameraVec3 camera_forward(const Camera* _cam);
// Returns the cameras current up direction (normalized)
extern CameraVec3 camera_up(const Camera* _cam);
// Returns the cameras current right direction (normalized)
extern CameraVec3 camera_right(const Camera* _cam);
// Returns the cameras current eye position
extern CameraVec3 camera_eye(const Camera* _cam);
// Move the camera in its relative orientation
// _offset == (forward, up, right)
extern void camera_move(Camera* _cam, const CameraVec3 _offset);
// Rotate the camera view
// _angles = (pitch, yaw, roll)
// pitch == "Look Up/Down" yaw == "Look Left/Right" roll == "Turn head Left/Right"
// Note: angles are expected in radians
extern void camera_rotate(Camera* _cam, const CameraVec3 _angles);
// Rotate the camera to look into the direction _forward
// This only changes the camera.orientation, it will still face its camera.target_position!
// Note: _forward and _up are expected to be normalized
extern void camera_look_at(Camera* _cam, CameraVec3 _forward, CameraVec3 _up);
// Update the camera and generate a view matrix
// Note: _out_matrix is expected to be a float[16]
extern void camera_view_matrix(Camera* _cam, float* _out_matrix);
#endif // !CAMERA_HEADER_GUARD
#ifdef CAMERA_IMPLEMENTATION
extern Camera camera_init()
{
static Camera cam = {
.target_position = cm_init_vec3(0.0f, 0.0f, 0.0f),
.target_distance = 0.0f,
.orientation = cm_init_quat(0.0f, 0.0f, 0.0, 0.0f),
.mode = CAMERA_MODE_FREE,
.movement_accumulator = cm_init_vec3(0.0f, 0.0f, 0.0f),
.rotation_accumulator = cm_init_vec3(0.0f, 0.0f, 0.0f),
.minPitch = 0.0f,
.maxPitch = 0.0f,
.minYaw = 0.0f,
.maxYaw = 0.0f,
.minRoll = 0.0f,
.maxRoll = 0.0f,
};
return cam;
};
extern CameraVec3 camera_forward(const Camera* _cam)
{
return cm_mul(CAMERA_WORLD_FORWARD, cm_invert(_cam->orientation));
};
extern CameraVec3 camera_up(const Camera* _cam)
{
return cm_mul(CAMERA_WORLD_UP, cm_invert(_cam->orientation));
};
extern CameraVec3 camera_right(const Camera* _cam)
{
return cm_mul(CAMERA_WORLD_RIGHT, cm_invert(_cam->orientation));
};
extern CameraVec3 camera_eye(const Camera* _cam)
{
return cm_add(_cam->target_position, cm_scale(camera_forward(_cam), -_cam->target_distance));
};
extern void camera_move(Camera* _cam, const CameraVec3 _offset)
{
_cam->movement_accumulator = cm_add(_cam->movement_accumulator, _offset);
}
extern void camera_rotate(Camera* _cam, const CameraVec3 _angles)
{
_cam->rotation_accumulator = cm_add(_cam->rotation_accumulator, _angles);
}
extern void camera_look_at(Camera* _cam, CameraVec3 _forward, CameraVec3 _up)
{
// Based on typical vector to matrix to quaternion approach
// Get orthogonal basis vectors
const CameraVec3 right = cm_normalizeVec3(cm_cross(_up, _forward));
_up = cm_cross(_forward, right);
// Convert to Quaternion
// Ref.: https://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/
const float m0 = right.x;
const float m1 = right.y;
const float m2 = right.z;
const float m4 = _up.x;
const float m5 = _up.y;
const float m6 = _up.z;
const float m8 = _forward.x;
const float m9 = _forward.y;
const float m10 = _forward.z;
float trace = m0 + m5 + m10;
if (trace > 0) {
float s = 0.5f / cm_sqrt(trace + 1.0f);
_cam->orientation.w = 0.25f / s;
_cam->orientation.x = (m6 - m9) * s;
_cam->orientation.y = (m8 - m2) * s;
_cam->orientation.z = (m1 - m4) * s;
}
else {
if (m0 > m5 && m0 > m10) {
float s = 2.0f * cm_sqrt(1.0f + m0 - m5 - m10);
_cam->orientation.w = (m6 - m9) / s;
_cam->orientation.x = 0.25f * s;
_cam->orientation.y = (m4 + m1) / s;
_cam->orientation.z = (m8 + m2) / s;
}
else if (m5 > m10) {
float s = 2.0f * cm_sqrt(1.0f + m5 - m0 - m10);
_cam->orientation.w = (m8 - m2) / s;
_cam->orientation.x = (m4 + m1) / s;
_cam->orientation.y = 0.25f * s;
_cam->orientation.z = (m9 + m6) / s;
}
else {
float s = 2.0f * cm_sqrt(1.0f + m10 - m0 - m5);
_cam->orientation.w = (m1 - m4) / s;
_cam->orientation.x = (m8 + m2) / s;
_cam->orientation.y = (m9 + m6) / s;
_cam->orientation.z = 0.25f * s;
}
}
}
extern void camera_view_matrix(Camera* _cam, float* _out_matrix)
{
/* Clamp angles */
if (_cam->mode & (CAMERA_MODE_CLAMP_PITCH_ANGLE | CAMERA_MODE_CLAMP_YAW_ANGLE | CAMERA_MODE_CLAMP_ROLL_ANGLE))
{
const CameraVec3 angles = cm_toEuler(_cam->orientation);
if (_cam->mode & CAMERA_MODE_CLAMP_PITCH_ANGLE)
{
_cam->rotation_accumulator.x = cm_max(_cam->minPitch - angles.x, _cam->rotation_accumulator.x);
_cam->rotation_accumulator.x = cm_min(_cam->maxPitch - angles.x, _cam->rotation_accumulator.x);
}
if (_cam->mode & CAMERA_MODE_CLAMP_YAW_ANGLE)
{
_cam->rotation_accumulator.y = cm_max(_cam->minYaw - angles.y, _cam->rotation_accumulator.y);
_cam->rotation_accumulator.y = cm_min(_cam->maxYaw - angles.y, _cam->rotation_accumulator.y);
}
if (_cam->mode & CAMERA_MODE_CLAMP_ROLL_ANGLE)
{
_cam->rotation_accumulator.z = cm_max(_cam->minRoll - angles.z, _cam->rotation_accumulator.z);
_cam->rotation_accumulator.z = cm_min(_cam->maxRoll - angles.z, _cam->rotation_accumulator.z);
}
}
/* Update orientation */
const CameraQuat pitch = cm_fromAxisAngle(CAMERA_WORLD_RIGHT, _cam->rotation_accumulator.x);
const CameraQuat yaw = cm_fromAxisAngle(CAMERA_WORLD_UP, _cam->rotation_accumulator.y);
if (_cam->mode & CAMERA_MODE_DISABLE_ROLL)
{
// Note: The multiplication order is important, not to induce roll from pitch+yaw
_cam->orientation = cm_mulQuat(_cam->orientation, pitch);
_cam->orientation = cm_mulQuat(yaw, _cam->orientation);
}
else
{
const CameraQuat roll = cm_fromAxisAngle(CAMERA_WORLD_FORWARD, _cam->rotation_accumulator.z);
_cam->orientation = cm_mulQuat(_cam->orientation, pitch);
_cam->orientation = cm_mulQuat(_cam->orientation, yaw);
_cam->orientation = cm_mulQuat(_cam->orientation, roll);
}
_cam->orientation = cm_normalizeQuat(_cam->orientation); // Re-Normalize orientation quaternion
// Reset accumulator
_cam->rotation_accumulator.x = 0.0f;
_cam->rotation_accumulator.y = 0.0f;
_cam->rotation_accumulator.z = 0.0f;
/* Update target_position */
CameraVec3 forward = camera_forward(_cam);
CameraVec3 up = camera_up(_cam);
CameraVec3 right = camera_right(_cam);
if (_cam->mode & CAMERA_MODE_MOVE_IN_WORLDPLANE)
{
const float epsilon = 0.0001f; // Avoid floating point errors
if (forward.y > 1.0f - epsilon) // Note: forward is normalized, so checking .y is sufficient
{ // Special case: Looking straight up
forward = cm_negate(up);
}
else if (forward.y < -1.0f + epsilon)
{ // Special case: Looking straight down
forward = up;
}
else if (right.y > 1.0f - epsilon)
{
right = up;
}
else if (right.y < -1.0f + epsilon)
{
right = cm_negate(up);
}
// Project the forward and right into the world plane
forward.y = 0;
forward = cm_normalizeVec3(forward);
right.y = 0;
right = cm_normalizeVec3(right);
up = CAMERA_WORLD_UP;
}
// Scale by desired distance
forward = cm_scale(forward, _cam->movement_accumulator.x);
up = cm_scale(up, _cam->movement_accumulator.y);
right = cm_scale(right, _cam->movement_accumulator.z);
// Apply changes to target_position
_cam->target_position = cm_add(_cam->target_position, forward);
_cam->target_position = cm_add(_cam->target_position, up);
_cam->target_position = cm_add(_cam->target_position, right);
// Reset accumulator
_cam->movement_accumulator.x = 0.0f;
_cam->movement_accumulator.y = 0.0f;
_cam->movement_accumulator.z = 0.0f;
/* Generate view matrix */
// Get rotation matrix
cm_matrixFromQuat(_out_matrix, _cam->orientation);
// Add translation
CameraVec3 translation = cm_negate(camera_eye(_cam));
translation = cm_mul(translation, _cam->orientation);
_out_matrix[12] = translation.x;
_out_matrix[13] = translation.y;
_out_matrix[14] = translation.z;
}
#endif // CAMERA_IMPLEMENTATION

107
src/lib/camera_math_bx.h Normal file
View File

@@ -0,0 +1,107 @@
/*
* INFO:
*
* This file provides an interface to math functions for camera.h
*
* Specifically interfacing with bx/math.h
* Ref.: https://github.com/bkaradzic/bgfx
* Ref.: https://github.com/bkaradzic/bx
*
*
* LICENSE:
*
* MIT License
*
* Copyright (c) 2022 Crydsch Cube
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <space_math.h>
#define CameraVec3 Vec3
#define CameraQuat Quat
static inline CameraVec3 cm_init_vec3(float _x, float _y, float _z) {
return bx::Vec3(_x, _y, _z);
}
static inline CameraQuat cm_init_quat(float _x, float _y, float _z, float _w) {
return bx::Quaternion(_x, _y, _z, _w);
}
static inline CameraVec3 cm_mul(CameraVec3 _a, CameraQuat _b) {
return bx::mul(_a, _b);
}
static inline CameraQuat cm_invert(CameraQuat _a) {
return bx::invert(_a);
}
static inline CameraVec3 cm_add(CameraVec3 _a, CameraVec3 _b) {
return bx::add(_a, _b);
}
static inline CameraVec3 cm_scale(CameraVec3 _a, float _b) {
return bx::mul(_a, _b);
}
static inline CameraVec3 cm_cross(CameraVec3 _a, CameraVec3 _b) {
return bx::cross(_a, _b);
}
static inline float cm_min(float _a, float _b) {
return bx::min(_a, _b);
}
static inline float cm_max(float _a, float _b) {
return bx::max(_a, _b);
}
static inline float cm_sqrt(float _a) {
return bx::sqrt(_a);
}
static inline CameraVec3 cm_toEuler(CameraQuat _a) {
return bx::toEuler(_a);
}
static inline CameraQuat cm_fromAxisAngle(CameraVec3 _a, float _b) {
return bx::fromAxisAngle(_a, _b);
}
static inline CameraQuat cm_mulQuat(CameraQuat _a, CameraQuat _b) {
return bx::mul(_a, _b);
}
static inline CameraQuat cm_normalizeQuat(CameraQuat _a) {
return bx::normalize(_a);
}
static inline CameraVec3 cm_normalizeVec3(CameraVec3 _a) {
return bx::normalize(_a);
}
static inline CameraVec3 cm_negate(CameraVec3 _a) {
return bx::neg(_a);
}
static inline void cm_matrixFromQuat(float* _a, CameraQuat _b) {
return bx::mtxFromQuaternion(_a, _b);
}

641
src/lib/input.cpp Normal file
View File

@@ -0,0 +1,641 @@
#include <assert.h>
#include <algorithm>
#include <functional>
#include <vector>
#include <set>
#include <cstring>
#include <cfloat>
#include "input.h"
#include "util.h"
#include "space_math.h"
/*
*
* buttons and axes keep state (need to be reset manually)
* axes are considered normalized by default
*
* Simple input handling system, with a fire-and-forget design.
* You configure it once, and it will handle the rest.
* It provides virtual inputs, so you can just poll for the state of your 'throw grenade button' in your gameloop!
* Any number of real buttons (keyboard, mouse, controller) can map to this virtual button.
*
* Features:
* - buttons and axes
* - Supports input sensitivity (via an input multiplier)
* - mappings between
* - buttons -> buttons
* - buttons -> axes (ex. keyboard input -> camera rotation)
* - axes -> buttons (ex. controller input -> fixed speed movement)
* - axes -> axes
* - multiple mappings to the same virtual input
* - 'w' or 'controller_axis_1' -> 'move_forward_axis'
* - combination mappings (only active if both real inputs are active)
* - 'shift' and 'w' -> 'running_button'
*/
namespace input {
// TODO remove_mapping(..) and set active=false
using Device = struct device {
uint16_t real_buttons_offset;
uint16_t real_axes_offset;
std::string name;
bool connected;
device(const uint16_t _real_buttons_offset, const uint16_t _real_axes_offset, std::string _name) {
real_buttons_offset = _real_buttons_offset;
real_axes_offset = _real_axes_offset;
name = std::move(_name);
connected = true;
}
};
using VirtualButton = struct virtual_button {
std::vector<uint16_t> real_inputs;
std::vector<float> multiplier;
};
using VirtualAxis = struct virtual_axis {
std::vector<uint16_t> real_inputs;
std::vector<float> multiplier;
float min;
float max;
virtual_axis(const float _min, const float _max) {
min = _min;
max = _max;
}
};
const uint16_t BUTTON_BIT = 0x1 << 15; // virtual input is button (instead of axis)
const uint16_t COMBINE_BIT = 0x1 << 14; // combine next input with logical 'and' (instead of 'or')
const uint16_t ID_MASK = COMBINE_BIT - 0x1; // bit mask to extract id
const float DEADZONE = 0.1f; // per axis deadzone
std::vector<Device> devices = { { 0, 0, "" }}; // Includes first dummy device
std::vector<DID> active_devices; // is online and has at least one mapping
std::vector<uint8_t> real_buttons_is = { };
std::vector<uint8_t> real_buttons_was = { };
std::vector<float> real_axes_is = { };
std::vector<float> real_axes_was = { };
std::vector<VirtualButton> virtual_buttons;
std::vector<VirtualAxis> virtual_axes;
inline uint16_t SET_BUTTON_BIT(uint16_t _id)
{
return _id | BUTTON_BIT;
}
inline bool GET_BUTTON_BIT(uint16_t _id)
{
return _id & BUTTON_BIT;
}
inline uint16_t SET_COMBINE_BIT(uint16_t _id)
{
return _id | COMBINE_BIT;
}
inline bool GET_COMBINE_BIT(uint16_t _id)
{
return _id & COMBINE_BIT;
}
inline uint16_t GET_ID(uint16_t _id)
{
return _id & ID_MASK;
}
inline float apply_deadzone(float _value) {
return std::abs(_value) < DEADZONE ? 0.0f : _value;
}
void update()
{
// Copy state
real_buttons_was = real_buttons_is;
real_axes_was = real_axes_is;
}
uint16_t get_device_num_buttons(const DID _device)
{
assert(_device < devices.size() - 1);
Device& device = devices[_device];
Device& device_next = devices[_device + 1];
return device_next.real_buttons_offset - device.real_buttons_offset;
}
uint16_t get_device_num_axes(const DID _device)
{
assert(_device < devices.size() - 1);
Device& device = devices[_device];
Device& device_next = devices[_device + 1];
return device_next.real_axes_offset - device.real_axes_offset;
}
// [internal]
bool device_has_mappings(const DID _device) {
assert(_device < devices.size() - 1);
// get range of absolute button & axis indices for this device
Device& device = devices[_device];
uint16_t btn_range_start = device.real_buttons_offset;
uint16_t btn_range_end = device.real_buttons_offset + get_device_num_buttons(_device) - 1;
uint16_t axis_range_start = device.real_axes_offset;
uint16_t axis_range_end = device.real_axes_offset + get_device_num_axes(_device) - 1;
// iterate all virtual buttons & axes
// check if any of their mappings refer to the set of device indices
for (uint16_t vbid = 0; vbid < virtual_buttons.size(); ++vbid) {
VirtualButton& btn = virtual_buttons[vbid];
for (uint16_t rid : btn.real_inputs) {
bool is_button = GET_BUTTON_BIT(rid);
uint16_t index = GET_ID(rid);
if ((is_button && between(index, btn_range_start, btn_range_end)) ||
(!is_button && between(index, axis_range_start, axis_range_end)))
{ // Found valid mapping!
return true;
}
}
}
for (uint16_t vid = 0; vid < virtual_axes.size(); ++vid) {
VirtualAxis& axis = virtual_axes[vid];
for (uint16_t rid : axis.real_inputs) {
bool is_button = GET_BUTTON_BIT(rid);
uint16_t index = GET_ID(rid);
if ((is_button && between(index, btn_range_start, btn_range_end)) ||
(!is_button && between(index, axis_range_start, axis_range_end)))
{ // Found valid mapping!
return true;
}
}
}
return false;
}
bool is_device_active(const DID _device) {
return std::find(active_devices.begin(), active_devices.end(), _device) != active_devices.end();
}
// TODO add GUID to device
DID add_device(const uint16_t _num_buttons, const uint16_t _num_axes, std::string _name)
{
// Try to re-identify a disconnected device
for (uint16_t id = 0; id < devices.size(); ++id) {
Device& device = devices[id];
if (!device.connected &&
get_device_num_buttons(id) == _num_buttons &&
get_device_num_axes(id) == _num_axes &&
device.name == _name)
{
// Found it!
logInfo("Re-identified input device ID: %d Buttons: %d Axes: %d Name: %s\n", id, _num_buttons, _num_axes, _name.c_str());
device.connected = true;
// Check if this device has mappings
assert(std::find(active_devices.begin(), active_devices.end(), id) == active_devices.end());
if (device_has_mappings(id)) {
active_devices.emplace_back(id);
}
return id;
}
}
// Add new device
DID id = (uint16_t)devices.size() - 1;
Device& device = devices[id];
// We now have the current dummy device
// Its offsets mark the current position on the bitset/vector
// Add new buttons
assert(real_buttons_is.size() == device.real_buttons_offset);
real_buttons_is.resize(device.real_buttons_offset + _num_buttons);
real_buttons_was.resize(device.real_buttons_offset + _num_buttons);
// Add new axes
assert(real_axes_is.size() == device.real_axes_offset);
real_axes_is.resize(device.real_axes_offset + _num_axes);
real_axes_was.resize(device.real_axes_offset + _num_axes);
logInfo("New input device ID: %d Buttons: %d Axes: %d Name: %s\n", id, _num_buttons, _num_axes, _name.c_str());
// Set device info
// no need to set offsets, they are already correct!
device.name = std::move(_name);
// Push new dummy device (marking new end)
devices.emplace_back(real_buttons_is.size(), real_axes_is.size(), "");
return id;
}
void remove_device(const DID _device) {
reset_device(_device);
Device& device = devices[_device];
device.connected = false;
// find and remove from active devices
auto it = std::find(active_devices.begin(), active_devices.end(), _device);
if (it != active_devices.end()) {
active_devices.erase(it);
}
}
void reset_device_buttons(const DID _device)
{
assert(_device < devices.size() - 1);
Device& device = devices[_device];
Device& device_next = devices[_device + 1];
// Reset all buttons
for (uint16_t i = device.real_buttons_offset; i < device_next.real_buttons_offset; ++i) {
real_buttons_is[i] = 0;
}
}
void reset_device_axes(const DID _device)
{
assert(_device < devices.size() - 1);
Device& device = devices[_device];
Device& device_next = devices[_device + 1];
// Reset all axes
for (uint16_t i = device.real_axes_offset; i < device_next.real_axes_offset; ++i) {
real_axes_is[i] = 0.0f;
}
}
void reset_device(const DID _device)
{
reset_device_buttons(_device);
reset_device_axes(_device);
}
void update_buttons(const DID _device, const uint16_t _start_index, const uint8_t* _new_states, const uint16_t _num_states)
{
assert(_device < devices.size() - 1);
assert(_new_states != nullptr);
assert(_start_index + _num_states <= get_device_num_buttons(_device));
Device& device = devices[_device];
std::memcpy(real_buttons_is.data() + device.real_buttons_offset + _start_index, _new_states, _num_states * sizeof(uint8_t));
}
void update_button(const DID _device, const uint16_t _index, const uint8_t _new_state)
{
update_buttons(_device, _index, &_new_state, 1);
}
template<bool accumulate>
void update_axes(const DID _device, const uint16_t _start_index, const float* _new_states, const uint16_t _num_states)
{
assert(_device < devices.size() - 1);
assert(_new_states != nullptr);
assert(_start_index + _num_states <= get_device_num_axes(_device));
Device& device = devices[_device];
float* dst = real_axes_is.data() + device.real_axes_offset + _start_index;
if constexpr (accumulate) {
for (uint16_t i = 0; i < _num_states; ++i) {
dst[i] += _new_states[i];
}
}
else {
std::memcpy(dst, _new_states, _num_states * sizeof(float));
}
}
void update_axes(const DID _device, const uint16_t _start_index, const float* _new_states, const uint16_t _num_states)
{
update_axes<false>(_device, _start_index, _new_states, _num_states);
}
void update_axes_acc(const DID _device, const uint16_t _start_index, const float* _new_states, const uint16_t _num_states)
{
update_axes<true>(_device, _start_index, _new_states, _num_states);
}
VBID add_virtual_button()
{
VBID vbid = (uint16_t)virtual_buttons.size();
virtual_buttons.emplace_back();
return vbid;
}
VAID add_virtual_axis(const float _min_value, const float _max_value)
{
VAID id = (uint16_t)virtual_axes.size();
virtual_axes.emplace_back(_min_value, _max_value);
return id;
}
void clear_mapping(const VBID _button)
{
assert(_button < virtual_buttons.size());
VirtualButton& v_button = virtual_buttons[_button];
v_button.real_inputs.clear();
v_button.multiplier.clear();
}
void clear_mapping(const VAID _axis)
{
assert(_axis < virtual_buttons.size());
// virtual axis
VirtualAxis& v_axis = virtual_axes[_axis];
v_axis.real_inputs.clear();
v_axis.multiplier.clear();
}
void map_button(const DID _device, const uint16_t _index, const VBID _button, const bool _combine_with_next)
{
assert(_device < devices.size() - 1);
assert(_index < get_device_num_buttons(_device));
assert(_button < virtual_buttons.size());
// get internal index of this button
Device& device = devices[_device];
uint16_t internal_id = device.real_buttons_offset + _index;
internal_id = SET_BUTTON_BIT(internal_id);
if (_combine_with_next) {
internal_id = SET_COMBINE_BIT(internal_id);
}
// virtual button
VirtualButton& v_button = virtual_buttons[_button];
v_button.real_inputs.emplace_back(internal_id);
v_button.multiplier.emplace_back(0.0f); // Button to Button mappings do not use multiplier
if (device.connected) {
// we added a mapping => mark device as active (if not already)
auto it = std::find(active_devices.begin(), active_devices.end(), _device);
if (it == active_devices.end()) {
active_devices.emplace_back(_device);
}
}
}
void map_button(const DID _device, const uint16_t _index, const VAID _axis, const float _multiplier, const bool _combine_with_next)
{
assert(_device < devices.size() - 1);
assert(_index < get_device_num_buttons(_device));
assert(_axis < virtual_axes.size());
// get internal index of this button
Device& device = devices[_device];
uint16_t internal_id = device.real_buttons_offset + _index;
internal_id = SET_BUTTON_BIT(internal_id);
if (_combine_with_next) {
internal_id = SET_COMBINE_BIT(internal_id);
}
// virtual axis
VirtualAxis& v_axis = virtual_axes[_axis];
v_axis.real_inputs.emplace_back(internal_id);
v_axis.multiplier.emplace_back(_multiplier);
if (device.connected) {
// we added a mapping => mark device as active (if not already)
auto it = std::find(active_devices.begin(), active_devices.end(), _device);
if (it == active_devices.end()) {
active_devices.emplace_back(_device);
}
}
}
void map_axis(const DID _device, const uint16_t _index, const VBID _button, const float _multiplier, const bool _combine_with_next)
{
assert(_device < devices.size() - 1);
assert(_index < get_device_num_axes(_device));
assert(_button < virtual_buttons.size());
// get internal index of this button
Device& device = devices[_device];
uint16_t internal_id = device.real_axes_offset + _index;
if (_combine_with_next) {
internal_id = SET_COMBINE_BIT(internal_id);
}
// virtual button
VirtualButton& v_button = virtual_buttons[_button];
v_button.real_inputs.emplace_back(internal_id);
v_button.multiplier.emplace_back(_multiplier);
if (device.connected) {
// we added a mapping => mark device as active (if not already)
auto it = std::find(active_devices.begin(), active_devices.end(), _device);
if (it == active_devices.end()) {
active_devices.emplace_back(_device);
}
}
}
void map_axis(const DID _device, const uint16_t _index, const VAID _axis, const float _multiplier, const bool _combine_with_next)
{
assert(_device < devices.size() - 1);
assert(_index < get_device_num_axes(_device));
assert(_axis < virtual_axes.size());
// get internal index of this button
Device& device = devices[_device];
uint16_t internal_id = device.real_axes_offset + _index;
if (_combine_with_next) {
internal_id = SET_COMBINE_BIT(internal_id);
}
// virtual axis
VirtualAxis& v_axis = virtual_axes[_axis];
v_axis.real_inputs.emplace_back(internal_id);
v_axis.multiplier.emplace_back(_multiplier);
if (device.connected) {
// we added a mapping => mark device as active (if not already)
auto it = std::find(active_devices.begin(), active_devices.end(), _device);
if (it == active_devices.end()) {
active_devices.emplace_back(_device);
}
}
}
// [internal]
template<bool in_current_frame>
bool button_down(const VBID _button) {
VirtualButton& v_button = virtual_buttons[_button];
bool down = false;
bool combination_group_down = true;
for (int i = 0; i < v_button.real_inputs.size(); ++i) {
uint16_t r_input = v_button.real_inputs[i];
bool is_button = GET_BUTTON_BIT(r_input);
bool is_combination = GET_COMBINE_BIT(r_input); // we have to AND it with the next one
uint16_t index = GET_ID(r_input);
bool tmp;
if (is_button) {
if constexpr (in_current_frame) {
tmp = real_buttons_is[index] > 0; // v_button.multiplier[i] ignored here
} else {
tmp = real_buttons_was[index] > 0; // v_button.multiplier[i] ignored here
}
}
else {
if constexpr (in_current_frame) {
tmp = (apply_deadzone(real_axes_is[index]) * v_button.multiplier[i]) > 0.5f;
}
else {
tmp = (apply_deadzone(real_axes_was[index]) * v_button.multiplier[i]) > 0.5f;
}
}
combination_group_down &= tmp;
if (!is_combination) {
// last combination group ended
down |= combination_group_down;
// reset combination group state
combination_group_down = true;
}
}
return down;
}
bool button_is_down(const VBID _button) {
return button_down<true>(_button);
}
bool button_was_down(const VBID _button) {
return button_down<false>(_button);
}
bool button_pressed(const VBID _button)
{
return button_is_down(_button) && !button_was_down(_button);
}
bool button_released(const VBID _button)
{
return button_was_down(_button) && !button_is_down(_button);
}
// [internal]
template<bool in_current_frame>
float axis_value(const VAID _axis)
{
assert(_axis < virtual_axes.size());
VirtualAxis& v_axis = virtual_axes[_axis];
float value = 0.0f;
float combination_group_value = FLT_MAX;
for (int i = 0; i < v_axis.real_inputs.size(); ++i) {
uint16_t r_input = v_axis.real_inputs[i];
bool is_button = GET_BUTTON_BIT(r_input);
bool is_combination = GET_COMBINE_BIT(r_input); // we have to AND it with the next one
uint16_t index = GET_ID(r_input);
float tmp;
if (is_button) {
if constexpr (in_current_frame) {
tmp = real_buttons_is[index] * v_axis.multiplier[i];
}
else {
tmp = real_buttons_was[index] * v_axis.multiplier[i];
}
}
else {
if constexpr (in_current_frame) {
tmp = apply_deadzone(real_axes_is[index]) * v_axis.multiplier[i];
}
else {
tmp = apply_deadzone(real_axes_was[index]) * v_axis.multiplier[i];
}
}
combination_group_value = std::min(combination_group_value, tmp);
if (!is_combination) {
// last combination group ended
value += combination_group_value;
// reset combination group state
combination_group_value = FLT_MAX;
}
}
return std::clamp(value, v_axis.min, v_axis.max);
}
float axis_value_is(const VAID _axis)
{
return axis_value<true>(_axis);
}
float axis_value_was(const VAID _axis)
{
return axis_value<false>(_axis);
}
const std::vector<DID>& get_active_devices() {
return active_devices;
}
void detect_input_callback(std::function<void()> _callback) {
}
void detect_input() {
// return did, is_button, index, value,
for (uint16_t did = 0; did < devices.size() - 1; ++did) {
Device& device = devices[did];
uint16_t num_buttons = get_device_num_buttons(did);
for (uint16_t btn_index = 0; btn_index < num_buttons; ++btn_index) {
uint8_t btn_is = real_buttons_is[device.real_buttons_offset + btn_index];
uint8_t btn_was = real_buttons_was[device.real_buttons_offset + btn_index];
if (btn_is != btn_was)
{ // change detected
logDebug("Device input detected: ID: %d Index: %d Is_Button: %d Value: %f\n", did, btn_index, 1, btn_is ? 1.0f : 0.0f);
}
}
uint16_t num_axes = get_device_num_axes(did);
for (uint16_t axis_index = 0; axis_index < num_axes; ++axis_index) {
float axis_is = apply_deadzone(real_axes_is[device.real_axes_offset + axis_index]);
float axis_was = apply_deadzone(real_axes_was[device.real_axes_offset + axis_index]);
if (axis_is != axis_was)
{ // change detected
logDebug("Device input detected: ID: %d Index: %d Is_Button: %d Value: %f\n", did, axis_index, 0, axis_is);
}
}
}
}
}

172
src/lib/input.h Normal file
View File

@@ -0,0 +1,172 @@
#pragma once
#include <string>
#include <vector>
#include <cstdint>
#include <cfloat>
namespace input {
// An ID used to identify devices
// Basically just a uint16_t - You can convert freely to and from it
// This type exists to aid correct usage
// Note: Returned ids are guaranteed to start from 0
using DID = struct device_id {
uint16_t id;
// default constructor
constexpr device_id() : id(0) {}
// implicit constructor from uint16_t
constexpr device_id(const uint16_t _id) : id(_id) {}
// implicit conversion to uint16_t
constexpr operator uint16_t() const { return id; }
};
// An ID used to identify virtual buttons
// Basically just a uint16_t - You can convert freely to and from it
// This type exists to aid correct usage
// Note: Returned ids are guaranteed to start from 0
using VBID = struct virtual_button_id {
uint16_t id;
// implicit constructor from uint16_t
constexpr virtual_button_id(const uint16_t _id) : id(_id) {}
// implicit conversion to uint16_t
constexpr operator uint16_t() const { return id; }
};
// An ID used to identify virtual axes
// Basically just a uint16_t - You can convert freely to and from it
// This type exists to aid correct usage
// Note: Returned ids are guaranteed to start from 0
using VAID = struct virtual_axis_id {
uint16_t id;
// implicit constructor from uint16_t
constexpr virtual_axis_id(const uint16_t _id) : id(_id) {}
// implicit conversion to uint16_t
constexpr operator uint16_t() const { return id; }
};
// Advance the internal state to the next frame.
// Should be called every frame BEFORE new input is updated
void update();
// Tries to re-identify an offline device with the same number
// of buttons, axes and matching name. Otherwise adds a new device
// Returns the ID of the device
// Note: Buttons and axes are indexed from 0 to num_<buttons/axes> - 1
DID add_device(const uint16_t _num_buttons, const uint16_t _num_axes, std::string _name);
// Marks it as offline
// The internal state is reset, but not actually removed
// A later call to 'add_device(..)' with the same parameters,
// may re-identify this device (and return its original DID)
void remove_device(const DID _device);
// Resets all buttons (to the state 'up') of the given device
void reset_device_buttons(const DID _device);
// Resets all axes (to 0.0f) of the given device
void reset_device_axes(const DID _device);
// Resets all buttons and all axes of the given device
void reset_device(const DID _device);
// Returns whether the device is active (connected and has at least one mapping)
bool is_device_active(const DID _device);
// Returns list of all active devices (devices that are connected and have at least one mapping)
// Useful to check which devices need to be updated
const std::vector<DID>& get_active_devices();
// Update multiple buttons of this device at once
// _start_index is the index of the first button to update
// _new_states must hold at least _num_states
void update_buttons(const DID _device, const uint16_t _start_index, const uint8_t* _new_states, const uint16_t _num_states);
// Update a single buttons state
void update_button(const DID _device, const uint16_t _index, const uint8_t _new_state);
// Update multiple axes of this device at once (overwriting the state)
// _start_index is the index of the first axis to update
// _new_states must hold at least _num_states
void update_axes(const DID _device, const uint16_t _start_index, const float* _new_states, const uint16_t _num_states);
// Update multiple axes of this device at once (accumulating to the state)
// _start_index is the index of the first axis to update
// _new_states must hold at least _num_states
void update_axes_acc(const DID _device, const uint16_t _start_index, const float* _new_states, const uint16_t _num_states);
// Update a real input buttons state (overwriting the state)
inline void update_axis(const DID _device, const uint16_t _index, const float _new_state)
{
update_axes(_device, _index, &_new_state, 1);
}
// Update a real input buttons state (accumulating to the state)
inline void update_axis_acc(const DID _device, const uint16_t _index, const float _new_state)
{
update_axes_acc(_device, _index, &_new_state, 1);
}
// Add a virtual button
VBID add_virtual_button();
// Add a virtual axis
// Its range will be confined to [_min_value; _max_value]
// Use defaults for unrestricted values or
// set [-1;1] for a normalized axis or
// typically you want set these to gameplay restriction ex.: max_walk_speed, max_camera_speed.
VAID add_virtual_axis(const float _min_value = -FLT_MAX, const float _max_value = FLT_MAX);
// Remove all mappings from this virtual button
void clear_mapping(const VBID _button);
// Remove all mappings from this virtual axis
void clear_mapping(const VAID _axis);
// Map a real button to a virtual button
// if _combine_with_next is true, this mapping will be combined with the next mapping applied to this virtual button
// Note: The last mapping in a combination group must have _combine_with_next == false
void map_button(const DID _device, const uint16_t _index, const VBID _button, const bool _combine_with_next = false);
// Map a real button to a virtual axis
// if _combine_with_next is true, this mapping will be combined with the next mapping applied to this virtual axis
// Note: The last mapping in a combination group must have _combine_with_next == false
void map_button(const DID _device, const uint16_t _index, const VAID _axis, const float _multiplier = 1.0f, const bool _combine_with_next = false);
// Map a real axis to a virtual button
// if _combine_with_next is true, this mapping will be combined with the next mapping applied to this virtual button
// Note: The last mapping in a combination group must have _combine_with_next == false
void map_axis(const DID _device, const uint16_t _index, const VBID _button, const float _multiplier = 1.0f, const bool _combine_with_next = false);
// Map a real axis to a virtual axis
// if _combine_with_next is true, this mapping will be combined with the next mapping applied to this virtual axis
// Note: The last mapping in a combination group must have _combine_with_next == false
void map_axis(const DID _device, const uint16_t _index, const VAID _axis, const float _multiplier = 1.0f, const bool _combine_with_next = false);
// Poll whether the virtual button is down in the current frame
bool button_is_down(const VBID _button);
// Poll whether the virtual button was down in the previous frame
bool button_was_down(const VBID _button);
// Poll whether the virtual button was pressed from the previous frame to the current frame
// This is a rising edge detection aka is_down && !was_down
bool button_pressed(const VBID _button);
// Poll whether the virtual button was released from the previous frame to the current frame
// This is a falling edge detection aka was_down && !is_down
bool button_released(const VBID _button);
// Poll axis value in the current frame
float axis_value_is(const VAID _axis);
// Poll axis value in the previous frame
float axis_value_was(const VAID _axis);
void detect_input();
}

8
src/lib/stb_image.cpp Normal file
View File

@@ -0,0 +1,8 @@
#define STB_IMAGE_IMPLEMENTATION
#define STB_IMAGE_WRITE_IMPLEMENTATION
#pragma warning( disable : 6262 26451)
#include "stb_image.h"
#include "stb_image_write.h"
#pragma warning( default : 6262 26451)
/// This file only exists as a translation unit for the library implementation

392
src/main.cpp Normal file
View File

@@ -0,0 +1,392 @@
#include <bx/platform.h>
#define GLFW_INCLUDE_NONE
#include <GLFW/glfw3.h>
#if BX_PLATFORM_LINUX || BX_PLATFORM_BSD
#define GLFW_EXPOSE_NATIVE_WAYLAND
#define GLFW_EXPOSE_NATIVE_X11
#elif BX_PLATFORM_WINDOWS
#define GLFW_EXPOSE_NATIVE_WIN32
#elif BX_PLATFORM_OSX
#define GLFW_EXPOSE_NATIVE_COCOA
#endif
#include <GLFW/glfw3native.h>
#include <bx/thread.h>
#include <bx/timer.h>
#include <bgfx/bgfx.h>
#include <bgfx/platform.h>
#include <thread>
#include <bx/thread.h>
#include <bx/timer.h>
#include <bgfx/bgfx.h>
#include <bgfx/platform.h>
#include "test.h"
#include "util.h"
#include "config.h"
#include "gameloop.h"
#include "space_input.h"
uint32_t window_width = config::MINIMAL_WINDOW_WIDTH;
uint32_t window_height = config::MINIMAL_WINDOW_HEIGHT;
bool cursor_entered_window = false;
GLFWgamepadstate gamepad_state;
static void glfw_error_callback(int error, const char *description)
{
logErr("GLFW error %d: %s\n", error, description);
}
static void glfw_window_size_callback(GLFWwindow *_window, int _window_width, int _window_height)
{
window_width = _window_width;
window_height = _window_height;
// Send window resize event to the game thread (to update the bgfx framebuffer size)
Event *ev = event_queue.begin_push();
ev->type = Resize;
event_queue.end_push();
}
static void glfw_window_focus_callback(GLFWwindow *_window, int _focused)
{
if (_focused)
{
glfwSetCursorPos(_window, 0, 0); // Avoid camera jump
}
}
static void glfw_key_callback(GLFWwindow *_window, int _key, int _scancode, int _action, int _mods)
{
if (_action == GLFW_REPEAT)
return; // We ignore key repeats
input::update_button_keycode(_scancode, _action == GLFW_PRESS);
}
static void glfw_mouse_button_callback(GLFWwindow *_window, int _button, int _action, int _mods)
{
input::update_button(input::Device::Mouse, _button, _action == GLFW_PRESS);
}
static void glfw_cursor_callback(GLFWwindow *_window, double _xpos, double _ypos)
{
// Avoid camera jump on (re-)entering the window
// ex. alt-tabbing
if (cursor_entered_window)
{
cursor_entered_window = false;
_xpos = 0.0; // Ignore first cursor movement
_ypos = 0.0;
}
const float pos[2] = {(float)_xpos, (float)_ypos};
// input::update_axis_acc(Device::Mouse, input::MOUSE_CURSOR_X_AXIS_INDEX, pos[0]);
// input::update_axis_acc(Device::Mouse, input::MOUSE_CURSOR_Y_AXIS_INDEX, pos[1]);
// Same as above
input::update_axes_acc(input::Device::Mouse, input::MouseAxis::CursorX, &pos[0], 2);
// reset internal mouse position to (0,0)
// this gives us relative coordinates every frame
glfwSetCursorPos(_window, 0, 0);
}
static void glfw_cursor_enter_callback(GLFWwindow *_window, int _entered)
{
cursor_entered_window = _entered;
}
static void glfw_scroll_callback(GLFWwindow *_window, double _xoffset, double _yoffset)
{
const float offset[2] = {(float)_xoffset, (float)_yoffset};
input::update_axes_acc(input::Device::Mouse, input::MouseAxis::ScrollX, &offset[0], 2);
}
static void glfw_joystick_callback(int _jid, int _event)
{
if (_event == GLFW_CONNECTED)
{
// get number of axes & buttons
int button_count = 0, axes_count = 0;
const uint8_t *buttons = glfwGetJoystickButtons(_jid, &button_count);
const float *axes = glfwGetJoystickAxes(_jid, &axes_count);
// get name
const char *name = glfwGetJoystickName(_jid);
if (axes == nullptr || buttons == nullptr || name == nullptr)
{
// Something is wrong with this joystick
// Maybe it was disconnected => do nothing
logWarn("Joystick connected, but cannot get data. (disconnected again?)\n");
return;
}
// tries to re-identify it or adds it as new
input::DID id = input::add_joystick(_jid, button_count, axes_count, std::string(name) + " (RAW)");
// Note: We do not update the state here, polling happens elsewhere
// is xbox mapping available?
if (glfwJoystickIsGamepad(_jid))
{
// get input state
int state_valid = glfwGetGamepadState(_jid, &gamepad_state);
if (state_valid == GLFW_FALSE)
{
// Something is wrong with this joystick
// Maybe it was disconnected => do nothing
logWarn("Joystick (JID: %d) has xbox mappings, but cannot get data. (disconnected again?)\n", _jid);
return;
}
// try to re-identify it or adds it as new
id = input::add_gamepad(_jid, std::string(name) + " (XBOX)");
// Note: We do not update the state here, polling happens elsewhere
// If no mappings yet => set default mappings
if (!input::is_joystick_active(_jid) &&
!input::is_gamepad_active(_jid))
{
input::set_gamepad_default_mappings(_jid);
}
}
}
else if (_event == GLFW_DISCONNECTED)
{
input::remove_joystick(_jid);
}
else
{
logWarn("Received unknown joystick event\n");
}
}
void handle_events(GLFWwindow *_window)
{
using input::Axis;
using input::Button;
using input::Device;
game_shutdown_requested = glfwWindowShouldClose(_window);
// Advance input system to next frame
input::update();
// Reset mouse axes
input::reset_device_axes(Device::Mouse);
glfwPollEvents();
// Poll joystick input
for (uint16_t jid : input::get_active_joysticks())
{
int button_count = 0, axes_count = 0;
const uint8_t *buttons = glfwGetJoystickButtons(jid, &button_count);
const float *axes = glfwGetJoystickAxes(jid, &axes_count);
if (axes == nullptr || buttons == nullptr)
{
// Something is wrong with this joystick
// Maybe it was disconnected => do nothing
logWarn("Joystick (JID: %d) error: Cannot get data (RAW). (disconnected?)\n", jid);
input::remove_joystick(jid);
continue;
}
input::update_joystick(jid, buttons, button_count, axes, axes_count);
}
for (uint16_t jid : input::get_active_gamepads())
{
// get input state
int state_valid = glfwGetGamepadState(jid, &gamepad_state);
if (state_valid == GLFW_FALSE)
{
// Something is wrong with this joystick
// Maybe it was disconnected => do nothing
logWarn("Joystick (JID: %d) error: Cannot get data (XBOX). (disconnected?)\n", jid);
input::remove_joystick(jid);
continue;
}
input::update_gamepad(jid, gamepad_state);
}
}
int main(int argc, char **argv)
{
logInfo("Starting spacegame :)\n");
// generate_vertices("C:/Users/Crydsch/Desktop/spacegame v5/shaders/verts.sh");
// generate_glsl_orientation_matrices("/home/crydsch/spacegame/shaders/orientations.sh");
// return 0;
// Run some debugging checks
#if not defined(NDEBUG)
if (!test())
exit(1);
#endif
// Fix for differences in debugging environment vs deploy environment
find_correct_working_directory();
logDebug("GLFW_PLATFORM_WAYLAND supported = %d\n", glfwPlatformSupported(GLFW_PLATFORM_WAYLAND));
logDebug("GLFW_PLATFORM_X11 supported = %d\n", glfwPlatformSupported(GLFW_PLATFORM_X11));
logDebug("GLFW_PLATFORM_WIN32 supported = %d\n", glfwPlatformSupported(GLFW_PLATFORM_WIN32));
// Create a GLFW window
#if BX_PLATFORM_LINUX || BX_PLATFORM_BSD
bool enable_wayland = should_use_wayland();
if (enable_wayland)
glfwInitHint(GLFW_PLATFORM, GLFW_PLATFORM_WAYLAND);
else
glfwInitHint(GLFW_PLATFORM, GLFW_PLATFORM_X11);
#elif BX_PLATFORM_WINDOWS
glfwInitHint(GLFW_PLATFORM, GLFW_PLATFORM_WIN32);
#elif BX_PLATFORM_OSX
glfwInitHint(GLFW_PLATFORM, GLFW_PLATFORM_COCOA);
#endif
glfwSetErrorCallback(glfw_error_callback);
if (!glfwInit())
DIE("Could not initialize GLFW\n");
int platform = glfwGetPlatform();
if (platform == GLFW_PLATFORM_WAYLAND)
logInfo("GLFW running on wayland\n");
else if (platform == GLFW_PLATFORM_X11)
logInfo("GLFW running on x11\n");
else if (platform == GLFW_PLATFORM_WIN32)
logInfo("GLFW running on win32\n");
else if (platform == GLFW_PLATFORM_COCOA)
logInfo("GLFW running on cocoa\n");
else
logInfo("GLFW running on unknown platform\n");
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
GLFWwindow *window = glfwCreateWindow(window_width, window_height, "spacegame", nullptr, nullptr);
if (!window)
DIE("Could not create GLFW window\n");
// Initialize input system
// Before we set glfw callbacks
input::init();
glfwSetWindowSizeLimits(window, config::MINIMAL_WINDOW_WIDTH, config::MINIMAL_WINDOW_HEIGHT, GLFW_DONT_CARE, GLFW_DONT_CARE);
glfwSetWindowSizeCallback(window, glfw_window_size_callback);
glfwSetWindowFocusCallback(window, glfw_window_focus_callback);
glfwSetCursorPos(window, 0, 0); // once before cursor callbacks
glfwSetCursorEnterCallback(window, glfw_cursor_enter_callback);
glfwSetKeyCallback(window, glfw_key_callback);
glfwSetMouseButtonCallback(window, glfw_mouse_button_callback);
glfwSetCursorPosCallback(window, glfw_cursor_callback);
glfwSetCursorPos(window, 0, 0); // once after cursor callbacks
glfwSetScrollCallback(window, glfw_scroll_callback);
glfwSetJoystickCallback(glfw_joystick_callback);
// Detect (already connected) joysticks
for (int i = 0; i < GLFW_JOYSTICK_LAST + 1; ++i)
{
if (glfwJoystickPresent(i))
{
glfw_joystick_callback(i, GLFW_CONNECTED);
}
}
// Setup cursor
#if NDEBUG
// Catch mouse (Only when not debugging; it will not release the mouse on a breakpoint..)
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
#endif
if (glfwRawMouseMotionSupported())
{
glfwSetInputMode(window, GLFW_RAW_MOUSE_MOTION, GLFW_TRUE);
}
// Call bgfx::renderFrame before bgfx::init to signal to bgfx not to create a render thread
// We do the threading ourselves
bgfx::renderFrame();
bgfx::PlatformData platformData;
#if BX_PLATFORM_LINUX || BX_PLATFORM_BSD
if (enable_wayland)
{
platformData.type = bgfx::NativeWindowHandleType::Wayland;
platformData.nwh = (void *)(uintptr_t)glfwGetWaylandWindow(window);
platformData.ndt = glfwGetWaylandDisplay();
}
else
{
platformData.type = bgfx::NativeWindowHandleType::Default;
platformData.nwh = (void *)(uintptr_t)glfwGetX11Window(window);
platformData.ndt = glfwGetX11Display();
}
#elif BX_PLATFORM_OSX
platformData.nwh = glfwGetCocoaWindow(window);
#elif BX_PLATFORM_WINDOWS
platformData.nwh = glfwGetWin32Window(window);
#endif
// Create a thread to call the bgfx API from (except bgfx::renderFrame)
bx::Thread gameloopThread;
gameloopThread.init(runGameloopThread, &platformData);
// Wait until bgfx is initialized
// this includes an initial dummy frame
// while (!bgfx_ready_flag.test()) {
while (!bgfx_ready_flag)
{
// Must be called repeatedly until bgfx is fully initialized
bgfx::renderFrame(100);
};
int64_t time_to_seconds = bx::getHPFrequency();
int64_t time_to_millis = time_to_seconds / 1000;
int64_t time_frame_start = 0;
// should be 16.666 for 60fps
// by slightly under estimating it, we might run slightly above 60fps, which is fine
int64_t frame_time_target_millis = 15; // todo is float neceed here?
// Main loop
while (!game_shutdown_requested)
{
// Handle glfw events
handle_events(window);
flag_signal(&input_ready_flag); // input is ready to be used
time_frame_start = bx::getHPCounter();
// Block until call to bgfx::frame, then
// Process submitted rendering primitives
bgfx::renderFrame();
flag_signal(&frame_ready_flag); // signal last frame was rendered
int64_t now = bx::getHPCounter();
int64_t frame_time_diff = (now - time_frame_start);
int64_t sleep = frame_time_target_millis - (frame_time_diff / time_to_millis);
std::this_thread::sleep_for(std::chrono::milliseconds(sleep));
// now = bx::getHPCounter();
// frame_time_diff = (now - time_frame_start);
// logDebug("FPS: %lf\n", 1000.0 / ((double)frame_time_diff / (double)time_to_millis));
}
// Wait for bgfx to shut down
while (bgfx::RenderFrame::NoContext != bgfx::renderFrame())
{
}
gameloopThread.shutdown(); // join game thread
glfwDestroyWindow(window);
glfwTerminate();
logInfo("Stopped spacegame gracefully. Till next time :)\n");
return gameloopThread.getExitCode();
}

20
src/net/debug_server.cpp Normal file
View File

@@ -0,0 +1,20 @@
#include "debug_server.h"
namespace debug_server {
void join(/*player id*/)
{
// TODO check player not already connected
// TODO register player joined
}
void simulate_server()
{
// TODO check registered event
// send updates to client
}
}

17
src/net/debug_server.h Normal file
View File

@@ -0,0 +1,17 @@
#pragma once
namespace debug_server {
// Requests to join a server
// Server will answer with surrounding grids
void join(/*player id*/);
// will "answer" requests
void simulate_server();
}

382
src/net/network.cpp Normal file
View File

@@ -0,0 +1,382 @@
//#include "network.h"
//#include "config.h"
//#include "util.h"
//#include <vector>
//#include "world.h"
//#include <unordered_map>
//#include "debug_server.h"
//
//// This system manages all network communication including:
//// Sending/Receiving client requests
//// Sending/Receiving server updates
//// Server <-> Client id translation
//
//// Request buffers
//std::unordered_map<uint32_t, ChunkRequest> chunk_requests; // chunk_PRid -> where on grid
//std::unordered_map<uint32_t, std::vector<BlockModelRequest>> block_model_requests;
//
//
//// ID mappings remote <-> local
//// Note: All ids here are pure!
//std::vector<uint32_t> RtoL_grid_ids(config::INITIAL_NUM_GRIDS, 0x0);
//std::vector<uint32_t> LtoR_grid_ids(config::INITIAL_NUM_GRIDS, 0x0);
//std::vector<uint32_t> RtoL_chunk_ids(config::INITIAL_NUM_CHUNKS, 0x0);
//std::vector<uint32_t> LtoR_chunk_ids(config::INITIAL_NUM_CHUNKS, 0x0);
//std::vector<uint32_t> RtoL_block_ids(config::INITIAL_NUM_BLOCKS, 0x0);
//std::vector<uint32_t> LtoR_block_ids(config::INITIAL_NUM_BLOCKS, 0x0);
//std::vector<uint32_t> RtoL_block_model_ids(config::INITIAL_NUM_BLOCK_MODELS, 0x0);
//std::vector<uint32_t> LtoR_block_model_ids(config::INITIAL_NUM_BLOCK_MODELS, 0x0);
//
//namespace net {
//
// // id translation functions
// // Pass one id and get reference to the translated one
// uint32_t& translate_grid_id_RtoL(const uint32_t _PRid)
// {
// while (_PRid >= RtoL_grid_ids.size())
// { // need to grow array
// RtoL_grid_ids.resize(2 * RtoL_grid_ids.size(), 0);
// }
//
// return RtoL_grid_ids[_PRid];
// }
// uint32_t& translate_grid_id_LtoR(const uint32_t _PLid)
// {
// while (_PLid >= LtoR_grid_ids.size())
// { // need to grow array
// LtoR_grid_ids.resize(2 * LtoR_grid_ids.size(), 0);
// }
//
// return LtoR_grid_ids[_PLid];
// }
//
// uint32_t& translate_chunk_id_RtoL(const uint32_t _PRid)
// {
// while (_PRid >= RtoL_chunk_ids.size())
// { // need to grow array
// RtoL_chunk_ids.resize(2 * RtoL_chunk_ids.size(), 0);
// }
//
// return RtoL_chunk_ids[_PRid];
// }
// uint32_t& translate_chunk_id_LtoR(const uint32_t _PLid)
// {
// while (_PLid >= LtoR_chunk_ids.size())
// { // need to grow array
// LtoR_chunk_ids.resize(2 * LtoR_chunk_ids.size(), 0);
// }
//
// return LtoR_chunk_ids[_PLid];
// }
//
// uint32_t& translate_block_id_RtoL(const uint32_t _PRid)
// {
// while (_PRid >= RtoL_block_ids.size())
// { // need to grow array
// RtoL_block_ids.resize(2 * RtoL_block_ids.size(), 0);
// }
//
// return RtoL_block_ids[_PRid];
// }
// uint32_t& translate_block_id_LtoR(const uint32_t _PLid)
// {
// while (_PLid >= LtoR_block_ids.size())
// { // need to grow array
// LtoR_block_ids.resize(2 * LtoR_block_ids.size(), 0);
// }
//
// return LtoR_block_ids[_PLid];
// }
//
// uint32_t& translate_block_model_id_RtoL(const uint32_t _PRid)
// {
// while (_PRid >= RtoL_block_model_ids.size())
// { // need to grow array
// RtoL_block_model_ids.resize(2 * RtoL_block_model_ids.size(), 0);
// }
//
// return RtoL_block_model_ids[_PRid];
// }
// uint32_t& translate_block_model_id_LtoR(const uint32_t _PLid)
// {
// while (_PLid >= LtoR_block_model_ids.size())
// { // need to grow array
// LtoR_block_model_ids.resize(2 * LtoR_block_model_ids.size(), 0);
// }
//
// return LtoR_block_model_ids[_PLid];
// }
//
// void join()
// {
// debug_server::join();
//
// // TODO send_CtoS_join
// }
//
// void recv_StoC_grid_update(StoC_grid_update _update)
// {
// uint32_t& grid_PLid = translate_grid_id_RtoL(_update.id);
//
// if (grid_PLid == 0)
// { // id does not exist => new grid
// if (_update.optional != 0b111)
// { // new grid MUST have all optional parameters
// logWarn("recv_StoC_grid_update received update for unknown grid. => ignoring it\n");
// return;
// }
//
// grid_PLid = world::add_grid();
// translate_grid_id_LtoR(grid_PLid) = _update.id;
//
// world::update_grid_transform(grid_PLid, _update.position, _update.orientation);
//
// for (uint32_t x = 0; x < 8; x++)
// for (uint32_t y = 0; y < 8; y++)
// for (uint32_t z = 0; z < 8; z++)
// world::update_grid_chunk(grid_PLid, x, y, z, _update.chunks_ids[x][y][z]);
//
// return;
// }
// // else id is valid grid
//
// // determine type of update
//
// if (_update.optional == 0b000)
// { // just id => grid got removed
//
// // TODO remove chunk id mappings
// // world::iterate_grid_chunks
// // RtoL_chunk_ids =0 ...
//
// world::remove_grid(grid_PLid);
//
// // remove id mapping
// translate_chunk_id_LtoR(grid_PLid) = 0;
// grid_PLid = 0;
// return;
// }
//
// if (_update.optional & 0b110)
// { // combined transform update
// world::update_grid_transform(grid_PLid, _update.position, _update.orientation);
// }
// else if (_update.optional & 0b100)
// { // position only
// world::update_grid_position(grid_PLid, _update.position);
// }
// else if (_update.optional & 0b010)
// { // orientation only
// world::update_grid_orientation(grid_PLid, _update.orientation);
// }
//
// if (_update.optional & 0b001)
// { // new chunk ids
// for (uint32_t x = 0; x < 8; x++)
// for (uint32_t y = 0; y < 8; y++)
// for (uint32_t z = 0; z < 8; z++)
// {
// uint32_t chunk_id = _update.chunks_ids[x][y][z]; // TODO rem when updating only valid ids
// if (chunk_id == 0) continue; // We only add new chunks, removal is done via chunk updates
//
// world::update_grid_chunk(grid_PLid, x, y, z, to_remote_id(chunk_id));
// }
// }
// }
//
// void subscribe_chunk(const uint32_t _PRid, ChunkRequest _req)
// {
// chunk_requests.insert(std::pair(_PRid, _req));
// send_CtoS_subscribe_chunk(_PRid);
// }
//
// void unsubscribe_requested_chunk(const uint32_t _PRid)
// {
//
// }
//
// void unsubscribe_local_chunk(const uint32_t _PRid, ChunkRequest _req)
// {
// auto req_it = chunk_requests.find(_PRid);
// if (req_it == chunk_requests.end())
// { // request is still pending => remove it
// chunk_requests.erase(req_it);
// }
// else
// {
// chunk_requests.insert(req_it, std::pair(_PRid, _req));
// }
// send_CtoS_unsubscribe_chunk(_PRid);
// }
//
// // instruct server to send chunk information (including blocks) AND keep us up-to-date
// void send_CtoS_subscribe_chunk(const uint32_t _PRid)
// {
// // TODO send request
// }
//
// void send_CtoS_unsubscribe_chunk(const uint32_t _PRid)
// { // instruct server to stop sending chunk updates
// // TODO send request
// }
//
// void recv_StoC_chunk_update(StoC_chunk_update _update)
// {
// // #1 DO WE KNOW THIS CHUNK?
// // translate id
// uint32_t& chunk_PLid = translate_chunk_id_RtoL(_update.id);
//
// // #1 NO
// if (chunk_PLid == 0)
// { // might be new chunk
// // #2 DID WE REQUEST IT?
// auto req_it = chunk_requests.find(_update.id);
// // #2 NO
// if (req_it == chunk_requests.end())
// { // this chunk was not requested! (or unsubscribed before we received this update)
// logTrace("recv_StoC_chunk_update received a non requested chunk => ignoring it\n");
// send_CtoS_unsubscribe_chunk(_update.id); // prevent further updates
// return;
// }
// // #2 YES
// ChunkRequest req = req_it->second;
// chunk_requests.erase(req_it);
//
// // fail-fast - does update indicate removed chunk?
// if (!_update.block_ids_exist)
// { // chunk does not/no longer exists on server
// return;
// }
//
// // add new chunk AND set id mapping
// chunk_PLid = world::add_chunk();
// translate_chunk_id_LtoR(chunk_PLid) = _update.id;
//
// // update parent
// world::update_grid_chunk(req.grid_id, req.offset_x, req.offset_y, req.offset_z, chunk_PLid);
//
// // Note:
// }
// // #1 YES
// // else chunk_PLid is known chunk
//
//
//
//
//
// // determine type of update
// if (!_update.block_ids_exist)
// { // just id => chunk got removed
// // update parent grid (set chunk id to original remote id)
// world::update_grid_chunk(req.grid_id, req.offset_x, req.offset_y, req.offset_z, to_remote_id(_update.id));
//
// world::remove_chunk(chunk_PLid);
//
// // remove id mapping
// translate_chunk_id_LtoR(chunk_PLid) = 0;
// chunk_PLid = 0;
// }
// else
// { // got new block ids
// for (uint32_t x = 0; x < 8; x++)
// for (uint32_t y = 0; y < 8; y++)
// for (uint32_t z = 0; z < 8; z++)
// {
// uint32_t block_PRid = _update.block_ids[x][y][z];
// uint32_t& block_PLid = translate_block_id_RtoL(block_PRid);
//
// if (block_PLid == 0) continue; // TODO rem when handling only valid updates
// // Note: block removal is done through block updates, not chunk updates
//
// block_PLid = world::add_block();
// world::update_chunk_block(chunk_PLid, x, y, z, block_PLid);
//
// // init block data
// auto& block_update = _update.block_data[x][y][z];
//
// world::update_block_rotation(block_PLid, block_update.rotation);
// world::update_block_base_model(block_PLid, to_remote_id(block_update.base_model_id));
// world::update_block_curr_model(block_PLid, to_remote_id(block_update.curr_model_id));
// }
//
// }
//
// }
//
// void recv_StoC_block_update(StoC_block_update _update)
// { // aka existing block got removed or changed (model, health, ...)
//
// }
//
// void request_block_model(const uint32_t _PRid, const uint32_t _requesting_block_PLid, const bool _request_base_model /* or curr_model?*/)
// {
// BlockModelRequest req = { .block_id = _requesting_block_PLid, .base_model = _request_base_model };
//
// auto req_it = block_model_requests.find(_PRid);
// if (req_it == block_model_requests.end())
// { // new request
// block_model_requests.insert(std::pair(_PRid, std::vector<BlockModelRequest>(1, req)));
// send_CtoS_get_block_model(_PRid);
// }
// else
// { // additional request => just enqueue requesting block
// std::vector<BlockModelRequest>& reqs = req_it->second;
// reqs.push_back(req);
// }
// }
//
// void send_CtoS_get_block_model(const uint32_t _PRid)
// {
//
// }
//
// void recv_StoC_block_model_update(StoC_block_model_update _update)
// {
// // translate id
// uint32_t& block_model_PLid = translate_block_model_id_RtoL(_update.id);
//
// if (block_model_PLid == 0)
// { // new block model
// auto req_it = block_model_requests.find(_update.id);
// if (req_it == block_model_requests.end())
// { // this model was not requested!
// logWarn("recv_StoC_block_model_update received a non requested block model => ignoring it\n");
// return;
// }
// // else was requested
// std::vector<BlockModelRequest> reqs = req_it->second;
// block_model_requests.erase(req_it);
//
// if (!_update.data_exists)
// { // model does not/no longer exist on server
// return;
// }
//
// // add model and id mapping
// block_model_PLid = world::add_block_model();
// translate_block_model_id_LtoR(block_model_PLid) = _update.id;
//
// // init model data
// world::update_block_model_data(block_model_PLid, _update.data, _update.num_data_elements);
//
// // update requesting blocks
// for (BlockModelRequest req : reqs)
// {
// if (req.base_model)
// {
// world::update_block_base_model(req.block_id, to_local_id(block_model_PLid));
// }
// else
// {
// world::update_block_curr_model(req.block_id, to_local_id(block_model_PLid));
// }
//
// }
//
// return;
// }
// // else block_model_PLid was already known
// logWarn("recv_StoC_block_model_update received update for already known block model => ignoring it\n");
//
// }
//}

83
src/net/network.h Normal file
View File

@@ -0,0 +1,83 @@
//#pragma once
//
//#include "space_math.h"
//
//typedef struct chunk_request {
// uint32_t grid_id;
// uint32_t offset_x;
// uint32_t offset_y;
// uint32_t offset_z;
//} ChunkRequest;
//
//typedef struct block_model_request {
// uint32_t block_id;
// bool base_model; // else was curr_model
//} BlockModelRequest;
//
//struct StoC_grid_update {
// // id
// uint32_t id;
// // 0b000 == grid removed
// // 0b1xx == includes position
// // 0bx1x == includes orientation
// // 0bxx1 == includes chunk ids
// uint8_t optional; // bitmap
// // opt: position
// Vec3 position;
// // opt: orientation
// Quat orientation;
// // opt: chunk_ids
// uint32_t chunks_ids[8][8][8]; // TODO better chunk_storage
//};
//
//struct CtoS_subscribe_chunk {
// // id
// uint32_t id;
//};
//
//struct CtoS_unsubscribe_chunk {
// // id
// uint32_t id;
//};
//
//struct StoC_chunk_update {
// // chunk id
// uint32_t id;
// // block_ids
// bool block_ids_exist; // TODO
// uint32_t block_ids[8][8][8];
//};
//
//struct StoC_chunk_block_data {
// uint8_t rotation;
// uint32_t base_model_id;
// uint32_t curr_model_id;
//};
//
//struct StoC_block_update {
// // block id
// uint32_t id;
// bool curr_model_id_exists;
// uint32_t curr_model_id; // may be ==base_model_id OR damaged variant
//};
//
//struct StoC_block_model_update {
// // id
// uint32_t id;
// bool data_exists;
// uint32_t num_data_elements;
// uint32_t* data;
//};
//
//
//namespace net {
//
// void join();
//
// void subscribe_chunk(const uint32_t _PRid, ChunkRequest _req);
//
// void unsubscribe_chunk(const uint32_t _PRid, ChunkRequest _req);
//
//
//
//}

97
src/renderer.cpp Normal file
View File

@@ -0,0 +1,97 @@
#include "renderer.h"
#include "util.h"
namespace renderer
{
void dispatch_cubes_cs(const Vec3 _camera_eye, const uint16_t _num_block_selection)
{
// uniform data
float ud[4] = {_camera_eye.x, _camera_eye.y, _camera_eye.z, float(_num_block_selection)};
bgfx::setUniform(gfx::get_uniform_cubes_compute_params(), ud);
bgfx::setBuffer(0, gfx::get_grid_buffer(), bgfx::Access::Read);
bgfx::setBuffer(1, gfx::get_chunk_buffer(), bgfx::Access::Read);
bgfx::setBuffer(2, gfx::get_block_buffer(), bgfx::Access::Read);
bgfx::setBuffer(3, gfx::get_block_selection_buffer(), bgfx::Access::Read);
bgfx::setBuffer(4, gfx::get_indirect_buffer(), bgfx::Access::Write);
bgfx::setBuffer(5, gfx::get_instance_buffer(), bgfx::Access::Write);
// Dispatch the call. We are using 64 local threads on the GPU to process the object list
// So lets dispatch ceil(numToDraw/64) workgroups of 64 local threads
// TODO investigate this number of threads...
bgfx::dispatch(0, gfx::get_cubes_compute_shader(), 64 /*uint32_t(num_objects / 64 + 1)*/, 1, 1);
}
void draw_cubes(const uint16_t _num_block_selection)
{
bgfx::setVertexBuffer(0, gfx::get_dummy_vertex_buffer()); // Some GPUs/drivers require a vertex buffer, even if we do not use it.
bgfx::setIndexBuffer(gfx::get_block_model_buffer());
bgfx::setTexture(0, gfx::get_uniform_texture_atlas_sampler(), gfx::get_texture_atlas(), BGFX_SAMPLER_POINT);
bgfx::setInstanceDataBuffer(gfx::get_instance_buffer(), 0, _num_block_selection);
bgfx::setState(0 | BGFX_STATE_WRITE_RGB
//| BGFX_STATE_WRITE_A
//| BGFX_STATE_BLEND_ALPHA
| BGFX_STATE_WRITE_Z | BGFX_STATE_DEPTH_TEST_LESS | BGFX_STATE_CULL_CW | BGFX_STATE_MSAA);
bgfx::submit(0, gfx::get_cubes_shader(), gfx::get_indirect_buffer(), 0, _num_block_selection);
}
void draw_lines(const uint32_t _ref, const Vec4 _color, const float *_transform_mtx)
{
/// Note: This uses bgfx line primitives.
/// These might not be supported on every platform.
/// Alternative 1: Generate lines as tiny quads ourselves.
/// Allows for line thickness. Maximum control.
/// Requires "line normal" estimation.
/// Cannot do fancy things like caps.
/// Alternative 2: Use par_shapes.h to generate 3D cylindrical shapes.
/// Allows for line thickness. Maximum control.
/// Requires library and generating structures.
/// May be very expensive.
/// Alternative 3: Use par_streamlines.h to generate 2D lines.
/// Allows for a variety of line renderings (caps).
/// Requires library and transforming 3D data into 2D.
/// Requires solving depth issues as lines are 2D.
uint32_t line_offset = 0, line_num_elems = 0;
gfx::get_line_offset(_ref, line_offset, line_num_elems);
bgfx::setVertexBuffer(0, gfx::get_line_buffer(), line_offset, line_num_elems);
bgfx::setUniform(gfx::get_uniform_line_color(), &_color);
bgfx::setTransform(_transform_mtx);
bgfx::setState(0 | BGFX_STATE_WRITE_RGB | BGFX_STATE_DEPTH_TEST_LEQUAL | BGFX_STATE_MSAA | BGFX_STATE_PT_LINES | BGFX_STATE_LINEAA);
bgfx::submit(0, gfx::get_lines_shader());
}
uint32_t crosshair_ref = 0; // gfx_line
float crosshair_mtx[16];
void draw_crosshair(const Camera *_cam)
{
vec3 eye = camera_eye(_cam);
vec3 forward = camera_forward(_cam);
assert(bx::isEqual(bx::length(forward), 1.0f, 0.001f));
Vec3 up = camera_up(_cam);
assert(bx::isEqual(bx::length(up), 1.0f, 0.001f));
Vec3 right = camera_right(_cam);
assert(bx::isEqual(bx::length(right), 1.0f, 0.001f));
eye = bx::add(eye, bx::mul(forward, 0.2f)); // in front of camera
GPULineVertex crosshair[4];
crosshair[0].position = bx::add(eye, bx::mul(up, -0.004f)); // down
crosshair[1].position = bx::add(eye, bx::mul(up, 0.004f)); // up
crosshair[2].position = bx::add(eye, bx::mul(right, -0.004f)); // left
crosshair[3].position = bx::add(eye, bx::mul(right, 0.004f)); // right
if (crosshair_ref == 0)
{ // create
crosshair_ref = gfx::add_line(crosshair, 4);
bx::mtxIdentity(crosshair_mtx);
}
gfx::update_line(crosshair_ref, crosshair);
renderer::draw_lines(crosshair_ref, {1.0f, 1.0f, 1.0f, 1.0f}, crosshair_mtx);
}
}

34
src/renderer.h Normal file
View File

@@ -0,0 +1,34 @@
#pragma once
#include "graphics.h"
#include "space_math.h"
#include "lib/camera.h"
/* * * * * * * * * * * * * * * * * * * * * * * */
/* The renderer namespace performs draw calls */
/* * * * * * * * * * * * * * * * * * * * * * * */
namespace renderer
{
void dispatch_cubes_cs(const Vec3 _camera_eye, const uint16_t _num_block_selection);
void draw_cubes(const uint16_t _num_block_selection);
// Draws lines referenced by _ref from the line buffer
void draw_lines(const uint32_t _ref, const Vec4 _color, const float *_transform_mtx);
void draw_crosshair(const Camera *_cam);
// void draw_outline(uint32_t _block_id, Vec3 _color) // block_id?
//{
// get block model -> outline offset/size in line buffer
// set color uniform
// set mvp uniform (grid+block transform)
// set vertex buffer
// set render state
//
// submit line shader call
//}
}

512
src/space_input.cpp Normal file
View File

@@ -0,0 +1,512 @@
#include <GLFW/glfw3.h>
#include <unordered_map>
#include <mutex>
#include <cfloat>
#include <algorithm>
#include "space_input.h"
#include "util.h"
// Note: We extend the input namespace with spacegame specific stuff
namespace input
{
using Joystick = struct joystick
{
DID raw;
DID xbox;
};
const uint16_t max_keyboard_keys = 128;
const float cursor_input_sensitivity = 0.002f; // TODO frame time based // maybe extend input system with global multiplier updated every frame?
const float max_camera_move_speed = 0.05f; // TODO frame time based
const float button_camera_move_speed = max_camera_move_speed; // TODO frame time based
const float button_camera_turn_speed = 0.03f; // TODO frame time based
std::unordered_map<int, uint16_t> keycode_map; // maps platform specific keycodes to key indices for the input system
uint16_t next_key_index = 0;
Joystick joystick_map[GLFW_JOYSTICK_LAST + 1] = {}; // maps glfw joystick ids to indices for the input system
std::vector<int> active_joysticks; // holds jids
std::vector<int> active_gamepads; // holds jids
std::vector<int> active_gamepads_tmp; // holds temporary jids for detection minigame
bool detect_minigame = false;
// convenience functions
inline void add_device(const Device _device, const uint16_t _num_buttons, const uint16_t _num_axes, std::string _name)
{
DID did = add_device(_num_buttons, _num_axes, _name);
assert(did == _device);
}
inline void add_button(const Button _button)
{
VBID vbid = add_virtual_button();
assert(vbid == _button);
}
inline void add_axis(const Axis _axis, const float _min_value = -FLT_MAX, const float _max_value = FLT_MAX)
{
VAID vaid = add_virtual_axis(_min_value, _max_value);
assert(vaid == _axis);
}
inline void map_button(const Device _device, const uint16_t _index, const Button _button, const bool _combine_with_next = false)
{
map_button((DID)_device, _index, (VBID)_button, _combine_with_next);
};
inline void map_button(const Device _device, const uint16_t _index, const Axis _axis, const float _multiplier = 1.0f, const bool _combine_with_next = false)
{
map_button((DID)_device, _index, (VAID)_axis, _multiplier, _combine_with_next);
};
inline void map_axis(const Device _device, const uint16_t _index, const Button _button, const float _multiplier = 1.0f, const bool _combine_with_next = false)
{
map_axis((DID)_device, _index, (VBID)_button, _multiplier, _combine_with_next);
};
inline void map_axis(const Device _device, const uint16_t _index, const Axis _axis, const float _multiplier = 1.0f, const bool _combine_with_next = false)
{
map_axis((DID)_device, _index, (VAID)_axis, _multiplier, _combine_with_next);
};
// Map a glfw named key
inline void map_key(const int _keyname /*GLFW_KEY_E*/, const Button _button, const bool _combine_with_next = false)
{
int keycode = glfwGetKeyScancode(_keyname);
if (keycode < 0)
{
assert(false); // Does this key not exist on the current platform?
return;
}
uint16_t index = 0;
auto it = keycode_map.find(keycode);
if (it == keycode_map.end())
{
// key is not mapped yet
if (next_key_index >= max_keyboard_keys)
{
assert(false); // cannot map more keys! Need to increase max_keyboard_keys
return;
}
index = next_key_index++;
keycode_map[keycode] = index;
}
else
{
// key is already mapped
index = it->second;
}
map_button(Device::Keyboard, index, _button, _combine_with_next);
}
inline void map_key(const int _keyname /*GLFW_KEY_E*/, const Axis _axis, const float _multiplier = 1.0f, const bool _combine_with_next = false)
{
int keycode = glfwGetKeyScancode(_keyname);
if (keycode < 0)
{
assert(false); // Does this key not exist on the current platform?
return;
}
uint16_t index = 0;
auto it = keycode_map.find(keycode);
if (it == keycode_map.end())
{
// key is not mapped yet
if (next_key_index >= max_keyboard_keys)
{
assert(false); // cannot map more keys! Need to increase max_keyboard_keys
return;
}
index = next_key_index++;
keycode_map[keycode] = index;
}
else
{
// key is already mapped
index = it->second;
}
map_button(Device::Keyboard, index, _axis, _multiplier, _combine_with_next);
}
// ex. for gamepad: _index == GLFW_GAMEPAD_BUTTON_A
inline void set_joystick_active(const int _jid)
{
assert(joystick_map[_jid].raw != 0);
auto it = std::find(active_joysticks.begin(), active_joysticks.end(), _jid);
if (it == active_joysticks.end())
{
active_joysticks.emplace_back(_jid);
}
}
inline void set_gamepad_active(const int _jid)
{
assert(joystick_map[_jid].xbox != 0);
auto it = std::find(active_gamepads.begin(), active_gamepads.end(), _jid);
if (it == active_gamepads.end())
{
active_gamepads.emplace_back(_jid);
}
}
inline void map_joystick_button(const int _jid, const uint16_t _index, const Button _button, const bool _combine_with_next = false)
{
DID did = joystick_map[_jid].raw;
assert(did != 0);
map_button(did, _index, (VBID)_button, _combine_with_next);
if (is_device_active(did))
{
set_joystick_active(_jid);
}
}
inline void map_joystick_button(const int _jid, const uint16_t _index, const Axis _axis, const float _multiplier = 1.0f, const bool _combine_with_next = false)
{
DID did = joystick_map[_jid].raw;
assert(did != 0);
map_button(did, _index, (VAID)_axis, _multiplier, _combine_with_next);
if (is_device_active(did))
{
set_joystick_active(_jid);
}
}
inline void map_joystick_axis(const int _jid, const uint16_t _index, const Button _button, const float _multiplier = 1.0f, const bool _combine_with_next = false)
{
DID did = joystick_map[_jid].raw;
assert(did != 0);
map_axis(did, _index, (VBID)_button, _multiplier, _combine_with_next);
if (is_device_active(did))
{
set_joystick_active(_jid);
}
}
inline void map_joystick_axis(const int _jid, const uint16_t _index, const Axis _axis, const float _multiplier = 1.0f, const bool _combine_with_next = false)
{
DID did = joystick_map[_jid].raw;
assert(did != 0);
map_axis(did, _index, (VAID)_axis, _multiplier, _combine_with_next);
if (is_device_active(did))
{
set_joystick_active(_jid);
}
}
inline void map_gamepad_button(const int _jid, const uint16_t _index, const Button _button, const bool _combine_with_next = false)
{
DID did = joystick_map[_jid].xbox;
assert(did != 0);
map_button(did, _index, (VBID)_button, _combine_with_next);
if (is_device_active(did))
{
set_gamepad_active(_jid);
}
}
inline void map_gamepad_button(const int _jid, const uint16_t _index, const Axis _axis, const float _multiplier = 1.0f, const bool _combine_with_next = false)
{
DID did = joystick_map[_jid].xbox;
assert(did != 0);
map_button(did, _index, (VAID)_axis, _multiplier, _combine_with_next);
if (is_device_active(did))
{
set_gamepad_active(_jid);
}
}
inline void map_gamepad_axis(const int _jid, const uint16_t _index, const Button _button, const float _multiplier = 1.0f, const bool _combine_with_next = false)
{
DID did = joystick_map[_jid].xbox;
assert(did != 0);
map_axis(did, _index, (VBID)_button, _multiplier, _combine_with_next);
if (is_device_active(did))
{
set_gamepad_active(_jid);
}
}
inline void map_gamepad_axis(const int _jid, const uint16_t _index, const Axis _axis, const float _multiplier = 1.0f, const bool _combine_with_next = false)
{
DID did = joystick_map[_jid].xbox;
assert(did != 0);
map_axis(did, _index, (VAID)_axis, _multiplier, _combine_with_next);
if (is_device_active(did))
{
set_gamepad_active(_jid);
}
}
void init()
{
// Add devices
add_device(Device::Keyboard, max_keyboard_keys, 0, "Keyboard");
add_device(Device::Mouse, GLFW_MOUSE_BUTTON_LAST + 1, MouseAxis::Count, "Mouse");
// Add virtual buttons
for (int i = 0; i < Button::Count; ++i)
{
add_button((Button)i);
}
// Add virtual Axes
add_axis(Axis::MoveForward, -max_camera_move_speed, max_camera_move_speed);
add_axis(Axis::MoveRight, -max_camera_move_speed, max_camera_move_speed);
add_axis(Axis::MoveUp, -max_camera_move_speed, max_camera_move_speed);
add_axis(Axis::CameraPitch); // Camera rotation is unrestricted
add_axis(Axis::CameraYaw); // Camera rotation is unrestricted
add_axis(Axis::CameraRoll); // Camera rotation is unrestricted
// Add default mapping
map_key(GLFW_KEY_F1, Button::ShowStats);
map_key(GLFW_KEY_F2, Button::LookAtOrigin);
map_key(GLFW_KEY_ESCAPE, Button::GameShouldExit);
map_key(GLFW_KEY_W, Axis::MoveForward, max_camera_move_speed);
map_key(GLFW_KEY_S, Axis::MoveForward, -max_camera_move_speed);
map_key(GLFW_KEY_D, Axis::MoveRight, max_camera_move_speed);
map_key(GLFW_KEY_A, Axis::MoveRight, -max_camera_move_speed);
map_key(GLFW_KEY_SPACE, Axis::MoveUp, max_camera_move_speed);
map_key(GLFW_KEY_LEFT_CONTROL, Axis::MoveUp, -max_camera_move_speed);
map_axis(Device::Mouse, MouseAxis::CursorX, Axis::CameraYaw, cursor_input_sensitivity);
map_axis(Device::Mouse, MouseAxis::CursorY, Axis::CameraPitch, cursor_input_sensitivity);
map_key(GLFW_KEY_LEFT, Axis::CameraYaw, -button_camera_turn_speed);
map_key(GLFW_KEY_RIGHT, Axis::CameraYaw, button_camera_turn_speed);
map_key(GLFW_KEY_UP, Axis::CameraPitch, -button_camera_turn_speed);
map_key(GLFW_KEY_DOWN, Axis::CameraPitch, button_camera_turn_speed);
map_button(Device::Mouse, GLFW_MOUSE_BUTTON_RIGHT, Button::PlaceBlock);
map_button(Device::Mouse, GLFW_MOUSE_BUTTON_LEFT, Button::RemoveBlock);
map_key(GLFW_KEY_E, Button::RotateBlockInc);
map_key(GLFW_KEY_Q, Button::RotateBlockDec);
map_axis(Device::Mouse, MouseAxis::ScrollY, Button::RotateBlockInc);
map_axis(Device::Mouse, MouseAxis::ScrollY, Button::RotateBlockDec, -1.0f);
}
void set_gamepad_default_mappings(const int _jid)
{
assert(joystick_map[_jid].xbox != 0); // is gamepad
map_gamepad_axis(_jid, GLFW_GAMEPAD_AXIS_LEFT_X, Axis::MoveRight, 0.08f);
map_gamepad_axis(_jid, GLFW_GAMEPAD_AXIS_LEFT_Y, Axis::MoveForward, 0.08f * -1.0f);
map_gamepad_axis(_jid, GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER, Axis::MoveUp, 0.08f);
map_gamepad_axis(_jid, GLFW_GAMEPAD_AXIS_LEFT_TRIGGER, Axis::MoveUp, 0.08f * -1.0f);
map_gamepad_axis(_jid, GLFW_GAMEPAD_AXIS_RIGHT_X, Axis::CameraYaw, 0.03f);
map_gamepad_axis(_jid, GLFW_GAMEPAD_AXIS_RIGHT_Y, Axis::CameraPitch, 0.03f);
map_gamepad_button(_jid, GLFW_GAMEPAD_BUTTON_LEFT_BUMPER, Button::PlaceBlock);
map_gamepad_button(_jid, GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER, Button::RemoveBlock);
map_gamepad_button(_jid, GLFW_GAMEPAD_BUTTON_START, Button::GameShouldExit);
}
void update_button_keycode(const int _keycode, const uint8_t _new_state)
{
auto it = keycode_map.find(_keycode);
if (it == keycode_map.end())
{
return; // ignore unmapped key
}
update_button(Device::Keyboard, it->second, _new_state);
}
DID add_joystick(const int _jid, const uint16_t _num_buttons, const uint16_t _num_axes, std::string _name)
{
logInfo("Joystick connected JID: %d\n", _jid);
// add device (may re-identify it and returns the correct id)
DID id = input::add_device(_num_buttons, _num_axes, _name);
assert(id != 0);
// update id mapping
joystick_map[_jid].raw = id;
if (is_device_active(id))
{
active_joysticks.emplace_back(_jid);
}
return id;
}
DID add_gamepad(const int _jid, std::string _name)
{
logInfo("Joystick (JID: %d) has xbox mappings\n", _jid);
// add device (may re-identify it and returns the correct id)
DID id = input::add_device(GLFW_GAMEPAD_BUTTON_LAST + 1, GLFW_GAMEPAD_AXIS_LAST + 1, _name);
assert(id != 0);
// update id mapping
joystick_map[_jid].xbox = id;
if (is_device_active(id))
{
active_gamepads.emplace_back(_jid);
}
return id;
}
void remove_joystick(const int _jid)
{
DID id = joystick_map[_jid].raw;
if (id > 0)
{
logInfo("Joystick (JID: %d) disconnected\n", _jid);
joystick_map[_jid].raw = 0; // reset mapping
remove_device(id);
// find and remove from active joysticks
auto it = std::find(active_joysticks.begin(), active_joysticks.end(), _jid);
if (it != active_joysticks.end())
{
active_joysticks.erase(it);
}
};
id = joystick_map[_jid].xbox;
if (id > 0)
{
joystick_map[_jid].xbox = 0; // reset mapping
remove_device(id);
// find and remove from active gamepads
auto it = std::find(active_gamepads.begin(), active_gamepads.end(), _jid);
if (it != active_gamepads.end())
{
active_gamepads.erase(it);
}
};
}
void update_joystick(const int _jid, const uint8_t *_buttons, const uint16_t _button_count, const float *_axes, const uint16_t _axes_count)
{
DID did = joystick_map[_jid].raw;
assert(did != 0);
input::update_buttons(did, 0, _buttons, _button_count);
input::update_axes(did, 0, _axes, _axes_count);
}
void update_gamepad(const int _jid, const GLFWgamepadstate &_state)
{
DID did = joystick_map[_jid].xbox;
assert(did != 0);
input::update_buttons(did, 0, _state.buttons, GLFW_GAMEPAD_BUTTON_LAST + 1);
input::update_axes(did, 0, _state.axes, GLFW_GAMEPAD_AXIS_LAST + 1);
}
bool is_joystick_active(const int _jid)
{
assert(joystick_map[_jid].raw != 0); // is joystick
return std::find(active_joysticks.begin(), active_joysticks.end(), _jid) != active_joysticks.end();
}
bool is_gamepad_active(const int _jid)
{
assert(joystick_map[_jid].xbox != 0); // is joystick
return std::find(active_gamepads.begin(), active_gamepads.end(), _jid) != active_gamepads.end();
}
const std::vector<int> &get_active_joysticks()
{
// Sanity check
#ifndef NDEBUG
auto ad = get_active_devices();
// All entries in active_joysticks are also active devices
for (auto jid : active_joysticks)
{
assert(joystick_map[jid].raw != 0);
assert(std::find(ad.begin(), ad.end(), joystick_map[jid].raw) != ad.end());
}
// All joysticks in active_devices are also active joysticks
for (auto d : ad)
{
for (int jid = 0; jid < GLFW_JOYSTICK_LAST + 1; ++jid)
{
if (d != 0 && d == joystick_map[jid].raw)
{ // d is joystick
assert(std::find(ad.begin(), ad.end(), jid) != ad.end());
}
}
}
#endif // NDEBUG
if (detect_minigame)
{ // return all (connected) joysticks
active_gamepads_tmp.clear();
for (int jid = 0; jid < GLFW_JOYSTICK_LAST + 1; ++jid)
{
if (joystick_map[jid].raw != 0)
{
active_gamepads_tmp.emplace_back(jid);
}
}
return active_gamepads_tmp;
}
return active_joysticks;
}
const std::vector<int> &get_active_gamepads()
{
// Sanity check
#ifndef NDEBUG
auto ad = get_active_devices();
// All entries in active_joysticks are also active devices
for (auto jid : active_gamepads)
{
assert(joystick_map[jid].xbox != 0);
assert(std::find(ad.begin(), ad.end(), joystick_map[jid].xbox) != ad.end());
}
// All joysticks in active_devices are also active joysticks
for (auto d : ad)
{
for (int jid = 0; jid < GLFW_JOYSTICK_LAST + 1; ++jid)
{
if (d != 0 && d == joystick_map[jid].xbox)
{ // d is joystick
assert(std::find(ad.begin(), ad.end(), jid) != ad.end());
}
}
}
#endif // NDEBUG
if (detect_minigame)
{ // return all (connected) gamepads
active_gamepads_tmp.clear();
for (int jid = 0; jid < GLFW_JOYSTICK_LAST + 1; ++jid)
{
if (joystick_map[jid].xbox != 0)
{
active_gamepads_tmp.emplace_back(jid);
}
}
return active_gamepads_tmp;
}
return active_gamepads;
}
void detect_minigame_start()
{
detect_minigame = true;
}
void detect_minigame_frame()
{
// must set active joysticks/gamepad to all connected
// or input will not be polled
// reset after detection is done
}
void detect_minigame_stop()
{
detect_minigame = false;
}
}

109
src/space_input.h Normal file
View File

@@ -0,0 +1,109 @@
#pragma once
#include <assert.h>
#include "lib/input.h"
// Note: We extend the input namespace with spacegame specific stuff
namespace input
{
namespace device_ns
{
enum device : uint16_t
{
Keyboard,
Mouse
};
}
using Device = device_ns::device;
namespace button_ns
{
enum button : uint16_t
{
// Debug & Internal actions
ShowStats,
LookAtOrigin,
GameShouldExit, // On window closed or menu exit
// Gameplay actions
PlaceBlock,
RemoveBlock,
RotateBlockDec,
RotateBlockInc,
Count
};
}
using Button = button_ns::button;
namespace axis_ns
{
enum axis : uint16_t
{
MoveForward,
MoveRight,
MoveUp,
CameraPitch,
CameraYaw,
CameraRoll,
Count
};
}
using Axis = axis_ns::axis;
namespace mouse_axis_ns
{
enum mouse_axis : uint16_t
{
CursorX,
CursorY,
ScrollX,
ScrollY,
Count
};
}
using MouseAxis = mouse_axis_ns::mouse_axis;
// Initialize input system
// Set default mapping
void init();
// Update the button state of a keyboard keycode
void update_button_keycode(const int _keycode, const uint8_t _new_state);
DID add_joystick(const int jid, const uint16_t _num_buttons, const uint16_t _num_axes, std::string _name);
// Adds a joystick as a gamepad
// Note: Use the same _jid to associate it with its raw counterpart
// Note: Automatically removed the corresponding joystick is removed
DID add_gamepad(const int jid, std::string _name);
void set_gamepad_default_mappings(const int _jid);
// Mark joystick as disconnected
void remove_joystick(const int _jid);
// Update joystick state (RAW)
void update_joystick(const int _jid, const uint8_t *_buttons, const uint16_t _button_count, const float *_axes, const uint16_t _axes_count);
// Update gamepad state (XBOX)
void update_gamepad(const int _jid, const GLFWgamepadstate &_state);
// Returns whether the given joystick is active (connected and at least one mapping)
bool is_joystick_active(const int _jid);
// Returns whether the given gamepad is active (connected and at least one mapping)
bool is_gamepad_active(const int _jid);
// Returns list of active joysticks (connected and at least one mapping)
// Useful to check which devices need to be updated
const std::vector<int> &get_active_joysticks();
// Returns list of active gamepads (connected and at least one mapping)
// Useful to check which devices need to be updated
const std::vector<int> &get_active_gamepads();
}

448
src/space_math.cpp Normal file
View File

@@ -0,0 +1,448 @@
#include "space_math.h"
constexpr Vec3 dir_to_normal_table[Direction::Count] = {
// PosX
Vec3(1.0f, 0.0f, 0.0f),
// NegX
Vec3(-1.0f, 0.0f, 0.0f),
// PosY
Vec3(0.0f, 1.0f, 0.0f),
// NegY
Vec3(0.0f, -1.0f, 0.0f),
// PosZ
Vec3(0.0f, 0.0f, 1.0f),
// NegZ
Vec3(0.0f, 0.0f, -1.0f),
};
Vec3 dir_to_normal(Direction _dir)
{
return dir_to_normal_table[_dir];
}
void transform_mtx(const Vec3 &_position, const Quat &_orientation, float *_out_mtx)
{
bx::mtxFromQuaternion(_out_mtx, _orientation);
bx::store(&_out_mtx[12], _position);
}
inline constexpr InvRay InverseRay(Ray &_ray)
{
return {
_ray.position,
{1.0f / _ray.direction.x,
1.0f / _ray.direction.y,
1.0f / _ray.direction.z}};
}
bool CheckCollisionRayBox(const InvRay &_ray, const AABB &_box, float &_out_distance)
{
static float t[8];
t[0] = (_box.min.x - _ray.position.x) * _ray.inverse_direction.x;
t[1] = (_box.max.x - _ray.position.x) * _ray.inverse_direction.x;
t[2] = (_box.min.y - _ray.position.y) * _ray.inverse_direction.y;
t[3] = (_box.max.y - _ray.position.y) * _ray.inverse_direction.y;
t[4] = (_box.min.z - _ray.position.z) * _ray.inverse_direction.z;
t[5] = (_box.max.z - _ray.position.z) * _ray.inverse_direction.z;
t[6] = bx::max(bx::max(bx::min(t[0], t[1]), bx::min(t[2], t[3])), bx::min(t[4], t[5]));
t[7] = bx::min(bx::min(bx::max(t[0], t[1]), bx::max(t[2], t[3])), bx::max(t[4], t[5]));
_out_distance = t[6];
return !(t[7] < 0 || t[6] > t[7]);
}
bool CheckCollisionRayOrientedBox(const Ray &_ray, const AABB &_box, const float *_inverse_model_matrix, float &_distance)
{
// We transform the ray "back" to the models origin where the axis aligned bounding box is valid
bx::Vec3 ray_origin = _ray.position;
bx::Vec3 ray_lookat = bx::add(_ray.position, _ray.direction);
Ray new_ray = {
.position = bx::mul(ray_origin, _inverse_model_matrix),
.direction = bx::mul(ray_lookat, _inverse_model_matrix),
};
new_ray.direction = bx::normalize(bx::sub(new_ray.direction, new_ray.position));
// now do normal AABB
return CheckCollisionRayBox(InverseRay(new_ray), _box, _distance);
}
// TODO check if it reports same results as matrix version
bool CheckCollisionRayOrientedBox(Ray _ray, const AABB &_box, const bx::Vec3 &_model_position, const bx::Quaternion &_model_orientation, float &_distance)
{
// We transform the ray "back" to the models origin where the axis aligned bounding box is valid
_ray.position = bx::sub(_ray.position, _model_position);
_ray.position = bx::mul(_ray.position, bx::invert(_model_orientation));
_ray.direction = bx::mul(_ray.direction, bx::invert(_model_orientation));
// now do normal AABB check
return CheckCollisionRayBox(InverseRay(_ray), _box, _distance);
}
// Check ray <-> AABB collision
// Optimized version with box.min==(0,0,0)
bool CheckCollisionRayBoxOrigin(const InvRay &_ray, const bx::Vec3 &_box_max, float &_distance)
{
static float t[8];
t[0] = (0.0f - _ray.position.x) * _ray.inverse_direction.x;
t[1] = (_box_max.x - _ray.position.x) * _ray.inverse_direction.x;
t[2] = (0.0f - _ray.position.y) * _ray.inverse_direction.y;
t[3] = (_box_max.y - _ray.position.y) * _ray.inverse_direction.y;
t[4] = (0.0f - _ray.position.z) * _ray.inverse_direction.z;
t[5] = (_box_max.z - _ray.position.z) * _ray.inverse_direction.z;
t[6] = bx::max(bx::max(bx::min(t[0], t[1]), bx::min(t[2], t[3])), bx::min(t[4], t[5]));
t[7] = bx::min(bx::min(bx::max(t[0], t[1]), bx::max(t[2], t[3])), bx::max(t[4], t[5]));
_distance = t[6];
return !(t[7] < 0 || t[6] > t[7]);
}
void combineAABB(AABB &_a, const AABB &_b)
{
_a.min.x = bx::min(_a.min.x, _b.min.x);
_a.min.y = bx::min(_a.min.y, _b.min.y);
_a.min.z = bx::min(_a.min.z, _b.min.z);
_a.max.x = bx::max(_a.max.x, _b.max.x);
_a.max.y = bx::max(_a.max.y, _b.max.y);
_a.max.z = bx::max(_a.max.z, _b.max.z);
}
float angle_between(const Vec3 _v0, const Vec3 _v1, const Vec3 _axis)
{
// Ref.: https://math.stackexchange.com/questions/878785/how-to-find-an-angle-in-range0-360-between-2-vectors
const float dot = bx::dot(_v0, _v1);
const float det = bx::dot(_axis, bx::cross(_v0, _v1));
return bx::atan2(det, dot);
}
float angle_between_faces(const BlockFace _f0, const BlockFace _f1, const Vec3 _common_edge)
{
// Note: The angle from face0 to face1 is the same as from neg(normal0) to normal1
// We use the common edge as the rotational axis
// const Vec3 n0 = toVec3(negate(_f0.normal));
// const Vec3 n1 = toVec3(_f1.normal);
// return angle_between(n0, n1, bx::normalize(_common_edge));
assert(false);
return 0.0f;
}
uint64_t reverse_bits(uint64_t b)
{
b = (b & 0xAAAAAAAAAAAAAAAA) >> 1 | (b & 0x5555555555555555) << 1;
b = (b & 0xCCCCCCCCCCCCCCCC) >> 2 | (b & 0x3333333333333333) << 2;
b = (b & 0xF0F0F0F0F0F0F0F0) >> 4 | (b & 0x0F0F0F0F0F0F0F0F) << 4;
b = (b & 0xFF00FF00FF00FF00) >> 8 | (b & 0xFF00FF00FF00FF00) << 8;
b = (b & 0xFFFF0000FFFF0000) >> 16 | (b & 0x0000FFFF0000FFFF) << 16;
b = b >> 32 | b << 32;
return b;
}
uint64_t reverse_bits_in_bytes(uint64_t b)
{
b = (b & 0xAAAAAAAAAAAAAAAA) >> 1 | (b & 0x5555555555555555) << 1;
b = (b & 0xCCCCCCCCCCCCCCCC) >> 2 | (b & 0x3333333333333333) << 2;
b = (b & 0xF0F0F0F0F0F0F0F0) >> 4 | (b & 0x0F0F0F0F0F0F0F0F) << 4;
return b;
}
// TODO return result in 'uint8_t[4] _out_res'
ComponentVertices get_component_face_vertices(FaceType _face, Direction _view_dir)
{
ComponentVertices res = {8, 8, 8, 8}; // init all as invalid
// TODO OPT try
// use Quad verts then rotate values (bytes in a uint32?)
// by (_face-2) positions to the left
// and set vert[3] = 8
switch (_face)
{
case FaceType::None :
return res;
case FaceType::Quad :
res = {2,0,1,3};
break;
case FaceType::TrigBL :
res = {2,0,1,8};
break;
case FaceType::TrigBR :
res = {0,1,3,8};
break;
case FaceType::TrigTR :
res = {1,3,2,8};
break;
case FaceType::TrigTL :
res = {3,2,0,8};
break;
default:
break;
}
// construct normalized orientation based on _view_dir
Orientation ori;
switch (_view_dir)
{
case Direction::PosX:
ori = Orientation(_view_dir, Direction::PosY);
break;
case Direction::NegX:
ori = Orientation(_view_dir, Direction::PosY);
break;
case Direction::PosY:
ori = Orientation(_view_dir, Direction::PosZ);
break;
case Direction::NegY:
ori = Orientation(_view_dir, Direction::PosZ);
break;
case Direction::PosZ:
ori = Orientation(_view_dir, Direction::PosY);
break;
case Direction::NegZ:
ori = Orientation(_view_dir, Direction::PosY);
break;
default:
assert(false);
return res;
}
// construct rot matrix from ori
I8RotMat3x3 mat(ori);
// rotate via matrix
res[0] = mat.rotateComponentVertex(res[0]);
res[1] = mat.rotateComponentVertex(res[1]);
res[2] = mat.rotateComponentVertex(res[2]);
if (_face == FaceType::Quad)
res[3] = mat.rotateComponentVertex(res[3]);
return res;
}
const struct component_face component_face::NONE =
{
.normal = FaceNormal(0, 1, 0), // irrelevant, but valid
.tex_apex = TexCorner::Count, // irrelevant
.vertices = {0, 0, 0, 0}, // irrelevant, and marking it as invalid
.texture_id = 0, // irrelevant
};
//////////////////////////////////////////////////////////////////////////////////////////////////////
/* Integer Math */
void mulMtxVec3(int8_t *_result, const int8_t *_mat, const int8_t *_vec)
{
_result[0] = _vec[0] * _mat[0] + _vec[1] * _mat[3] + _vec[2] * _mat[6];
_result[1] = _vec[0] * _mat[1] + _vec[1] * _mat[4] + _vec[2] * _mat[7];
_result[2] = _vec[0] * _mat[2] + _vec[1] * _mat[5] + _vec[2] * _mat[8];
}
void mulMtxMtx(int8_t *_result, const int8_t *_a, const int8_t *_b)
{
mulMtxVec3(&_result[ 0], &_a[ 0], _b);
mulMtxVec3(&_result[ 3], &_a[ 3], _b);
mulMtxVec3(&_result[ 6], &_a[ 6], _b);
}
i8rotmat3x3::i8rotmat3x3(const Orientation _ori)
{
Direction forward, up;
_ori.to_dirs(forward, up);
Direction right = rotate(forward, up);
I8Vec3 x(right);
I8Vec3 y(up);
I8Vec3 z(forward);
m[0] = x.x;
m[1] = x.y;
m[2] = x.z;
m[3] = y.x;
m[4] = y.y;
m[5] = y.z;
m[6] = z.x;
m[7] = z.y;
m[8] = z.z;
}
I8Vec3 i8rotmat3x3::mul(const I8Vec3 &_vec)
{
I8Vec3 res;
mulMtxVec3(&res.x, m, &_vec.x);
return res;
}
i8rotmat3x3 i8rotmat3x3::mul(const i8rotmat3x3 &_m)
{
i8rotmat3x3 res;
mulMtxMtx(res.m, m, _m.m);
return res;
}
i8rotmat3x3 i8rotmat3x3::transpose()
{
// [0] [3] [6] [0] [1] [2]
// [1] [4] [7] => [3] [4] [5]
// [2] [5] [8] [6] [7] [8]
i8rotmat3x3 res;
res.m[0] = m[0];
res.m[1] = m[3];
res.m[2] = m[6];
res.m[3] = m[1];
res.m[4] = m[4];
res.m[5] = m[7];
res.m[6] = m[2];
res.m[7] = m[5];
res.m[8] = m[8];
return res;
}
// _vert MUST be in [0;7]
uint8_t i8rotmat3x3::rotateComponentVertex(uint8_t _vert)
{
assert(_vert >= 0 && _vert <= 7);
I8Vec3 vec(_vert);
vec = mul(vec);
assert(vec.x >= -1 && vec.x <= 1 &&
vec.y >= -1 && vec.y <= 1 &&
vec.z >= -1 && vec.z <= 1);
return vec.toCompVert();
}
FaceNormal i8rotmat3x3::rotateFaceNormal(const FaceNormal _normal)
{
return FaceNormal(mul(_normal));
}
Direction i8rotmat3x3::rotateDirection(Direction _dir)
{
return mul(I8Vec3(_dir)).toDirection();
}
FaceType component_face::faceType(Direction* _out_view_dir) const
{
using namespace direction_ns;
// Faces are interpreted in normalized orientation
// up== PosY when forward== {PosX,NegX,PosZ,NegZ}
// and up== PosZ when forward== {PosY,NegY}
// vertices => type + direction
if (_out_view_dir)
*_out_view_dir = Direction::Count;
if (!isValid())
return FaceType::None;
// ! The triangle apex determines the type !
// determine direction
// vertices are 3 bits representing z,y,x respectively
// & will leave only the common 1 bit => aka the direction
// if x=1,y=1 or z=1, if the common bit is 0 however
// we need to negate first
// => copy the 3 bits over and to everything in one go
uint8_t bits = vertices[0] & vertices[1] & vertices[2];
uint8_t bits2 = (~vertices[0] & ~vertices[1] & ~vertices[2]) & 0b111;
bits |= (bits2 << 3);
// Now the bits are:
// [0|0|PosZ|PosY|PosX|NegZ|NegY|NegX]
Direction dir;
Direction up;
switch (bits)
{
case 1:
dir = NegX;
up = PosY;
break;
case 2:
dir = NegY;
up = PosZ;
break;
case 4:
dir = NegZ;
up = PosY;
break;
case 8:
dir = PosX;
up = PosY;
break;
case 16:
dir = PosY;
up = PosZ;
break;
case 32:
dir = PosZ;
up = PosY;
break;
default:
break;
}
if (_out_view_dir)
*_out_view_dir = dir;
if (isQuad())
return FaceType::Quad;
// Alternatively we could maybe?
// Shuffle bits to be
// [0|0|NegZ|PosZ|NegY|PosY|NegX|PosX]
// and just
// Direction dir = (Direction)bx::countTrailingZeros(bits);
// rotate apex in reverse
I8RotMat3x3 mat(Orientation(dir, up));
mat = mat.transpose();
uint8_t vert = mat.rotateComponentVertex(vertices[1]);
// match with "front" vertices
switch (vert)
{
case 0:
return FaceType::TrigBL;
case 1:
return FaceType::TrigBR;
case 2:
return FaceType::TrigTL;
case 3:
return FaceType::TrigTR;
default:
break;
}
return FaceType::None;
}
Direction i8vec3::toDirection()
{
assert(x >= -1 && x <= 1 &&
y >= -1 && y <= 1 &&
z >= -1 && z <= 1 &&
(x + y + z != 0));
return (Direction)((x == -1) +
(y == 1) * 2 + (y == -1) * 3 +
(z == 1) * 4 + (z == -1) * 5);
}
uint8_t face_normal::dims()
{
uint8_t res = 0;
res += x != 0;
res += y != 0;
res += z != 0;
return res;
}

821
src/space_math.h Normal file
View File

@@ -0,0 +1,821 @@
#pragma once
#include <bx/math.h>
#include <assert.h>
#include <functional>
#include <array>
#include <cstddef>
#include <utility>
#include <algorithm>
#include <ranges>
// Dummy defines for broken intellisense
// #define size_t (unsigned long int)
// Note: bx structs behave a bit strange (ex. deleted default constructor)
// For ease of use we provide our own equivalents with implicit casting
// to and from the corresponding bx types.
// TODO move all lookup tables into .cpp without constexpr
// => constexpr copies it into each translation unit!
typedef struct vec3
{
float x, y, z;
vec3() = default;
~vec3() = default;
constexpr vec3(const float _v) : x(_v), y(_v), z(_v) {};
constexpr vec3(const float _x, const float _y, const float _z)
: x(_x), y(_y), z(_z) {}
// User-defined implicit constructor from bx::Vec3
constexpr vec3(const bx::Vec3 &_v) : x(_v.x), y(_v.y), z(_v.z) {}
// User-defined implicit conversion to bx::Vec3
constexpr operator bx::Vec3() const { return bx::Vec3(x, y, z); }
} Vec3;
typedef struct orientation Orientation;
typedef struct face_normal FaceNormal;
typedef struct vec4
{
float x, y, z, w;
vec4() = default;
~vec4() = default;
constexpr vec4(const float _v) : x(_v), y(_v), z(_v), w(_v) {};
constexpr vec4(const float _x, const float _y, const float _z, const float _w)
: x(_x), y(_y), z(_z), w(_w) {}
} Vec4;
typedef struct quat
{
float x, y, z, w;
quat() = default;
~quat() = default;
constexpr quat(const float _x, const float _y, const float _z, const float _w)
: x(_x), y(_y), z(_z), w(_w) {}
// User-defined implicit constructor from bx::Quaternion
constexpr quat(const bx::Quaternion &_v) : x(_v.x), y(_v.y), z(_v.z), w(_v.w) {}
// User-defined implicit conversion to bx::Quaternion
constexpr operator bx::Quaternion() const { return bx::Quaternion(x, y, z, w); }
static constexpr quat unit()
{
return quat(0.0f, 0.0f, 0.0f, 1.0f);
}
} Quat;
typedef struct color
{
uint8_t r, g, b, a;
color() : r(0), g(0), b(0), a(1) {}
color(uint8_t _r, uint8_t _g, uint8_t _b, uint8_t _a) : r(_r), g(_g), b(_b), a(_a) {}
} Color;
typedef Color Texel;
typedef struct ray
{
Vec3 position;
Vec3 direction;
} Ray;
typedef struct inv_ray
{
Vec3 position;
Vec3 inverse_direction;
} InvRay;
typedef struct aabb
{
Vec3 min;
Vec3 max;
aabb() = default;
~aabb() = default;
constexpr aabb(const Vec3 _min, const Vec3 _max) : min(_min), max(_max) {};
} AABB;
namespace direction_ns
{
enum direction : uint8_t
{
// The order MUST stay like this
// Other systems rely on this exact order
// e.g. FaceNormals
PosX,
NegX,
PosY,
NegY,
PosZ,
NegZ,
Count
};
}
using Direction = direction_ns::direction;
constexpr Direction negate(const Direction _dir)
{
// +1 if even or -1 if odd
return (Direction)((uint8_t)_dir ^ 0x1);
}
// TODO OPT bitpack or padding
typedef struct i8vec3
{
int8_t x, y, z;
constexpr i8vec3() : x(0), y(0), z(0) {}
constexpr i8vec3(const int8_t _x, const int8_t _y, const int8_t _z) : x(_x), y(_y), z(_z)
{
}
constexpr i8vec3(Direction _dir)
{
using namespace direction_ns;
x = (_dir > 1) ? 0 : (_dir == PosX) ? 1 : -1;
z = (_dir < 4) ? 0 : (_dir == PosZ) ? 1 : -1;
// x==z only if x==z==0
y = (x != z) ? 0 : (_dir == PosY) ? 1 : -1;
}
constexpr i8vec3(uint8_t _comp_vert) {
x = ((_comp_vert & 0b001) == 0) ? -1 : 1;
y = ((_comp_vert & 0b010) == 0) ? -1 : 1;
z = ((_comp_vert & 0b100) == 0) ? -1 : 1;
}
i8vec3 negate()
{
return i8vec3(-x, -y, -z);
}
// Must be a valid conversion (no safety checks)
Direction toDirection();
uint8_t toCompVert() {
return (x == 1) + (y == 1) * 2 + (z == 1) * 4;
}
auto operator<=>(const i8vec3&) const = default;
} I8Vec3;
static_assert(sizeof(I8Vec3) == 3);
// A integer based rotation matrix
// Values should only be in [-1;1] and only encode valid rotations
typedef struct i8rotmat3x3
{
// [0] [3] [6]
// [1] [4] [7]
// [2] [5] [8]
int8_t m[9] = {};
i8rotmat3x3() {}
i8rotmat3x3(const Orientation _ori);
// consider the static version of this method: mulMtxVec3
I8Vec3 mul(const I8Vec3& _vec);
// consider the static version of this method: mulMtxMtx
i8rotmat3x3 mul(const i8rotmat3x3& _vec);
// Note: For a rotation matrix the inverse IS the transpose
i8rotmat3x3 transpose();
uint8_t rotateComponentVertex(uint8_t _vert);
FaceNormal rotateFaceNormal(const FaceNormal _normal);
Direction rotateDirection(Direction _dir);
// we want real, but integer based matrix multiplication
// specifically for rotations
// convert to and from orientations
// ori -> directions(up, forward) -> to vectors
// match base vectors with directions -> ori from dirs
// must be able to rotate
// - component vertices
// - face normals (and therefore directions)
} I8RotMat3x3;
// TODO OBB ?
// but with vec3 & quat - right? <.<
// reverses all bits i.e. bit#0 is swapped with bit#63, bit#1 with bit#62, ...
uint64_t reverse_bits(uint64_t b);
// reverse all bits in each byte i.e. bit#0 is swapped with bit#7, bit#1 with bit6,...
uint64_t reverse_bits_in_bytes(uint64_t b);
constexpr Direction rotate_dir[7 * 7] =
{
// Manually defined direction rotation
// PosX*6+PosX
Direction::PosX,
// PosX*6+NegX
Direction::PosX,
// PosX*6+PosY
Direction::NegZ,
// PosX*6+NegY
Direction::PosZ,
// PosX*6+PosZ
Direction::PosY,
// PosX*6+NegZ
Direction::NegY,
// PosX*6+Count
Direction::Count,
// NegX*6+PosX
Direction::NegX,
// NegX*6+NegX
Direction::NegX,
// NegX*6+PosY
Direction::PosZ,
// NegX*6+NegY
Direction::NegZ,
// NegX*6+PosZ
Direction::NegY,
// NegX*6+NegZ
Direction::PosY,
// NegX*6+Count
Direction::Count,
// PosY*6+PosX
Direction::PosZ,
// PosY*6+NegX
Direction::NegZ,
// PosY*6+PosY
Direction::PosY,
// PosY*6+NegY
Direction::PosY,
// PosY*6+PosZ
Direction::NegX,
// PosY*6+NegZ
Direction::PosX,
// PosY*6+Count
Direction::Count,
// NegY*6+PosX
Direction::NegZ,
// NegY*6+NegX
Direction::PosZ,
// NegY*6+PosY
Direction::NegY,
// NegY*6+NegY
Direction::NegY,
// NegY*6+PosZ
Direction::PosX,
// NegY*6+NegZ
Direction::NegX,
// NegY*6+Count
Direction::Count,
// PosZ*6+PosX
Direction::NegY,
// PosZ*6+NegX
Direction::PosY,
// PosZ*6+PosY
Direction::PosX,
// PosZ*6+NegY
Direction::NegX,
// PosZ*6+PosZ
Direction::PosZ,
// PosZ*6+NegZ
Direction::PosZ,
// PosZ*6+Count
Direction::Count,
// NegZ*6+PosX
Direction::PosY,
// NegZ*6+NegX
Direction::NegY,
// NegZ*6+PosY
Direction::NegX,
// NegZ*6+NegY
Direction::PosX,
// NegZ*6+PosZ
Direction::NegZ,
// NegZ*6+NegZ
Direction::NegZ,
// NegZ*6+Count
Direction::Count,
// Count*6+PosX
Direction::Count,
// Count*6+NegX
Direction::Count,
// Count*6+PosY
Direction::Count,
// Count*6+NegY
Direction::Count,
// Count*6+PosZ
Direction::Count,
// Count*6+NegZ
Direction::Count,
// Count*6+Count
Direction::Count,
};
// Rotates _dir by 90deg around _axis according to the left-claw-rule
// Supports Direction::Count as input and always returns Count then
constexpr Direction rotate(Direction _dir, Direction _axis)
{
return rotate_dir[_dir * 7 + _axis];
}
Vec3 dir_to_normal(Direction _dir);
constexpr uint8_t pack_dirs(const Direction _forward, const Direction _up)
{
// each dir is 3 bit wide
return (_forward << 3) | _up;
}
constexpr void unpack_dirs(const uint8_t _packed, Direction &_out_forward, Direction &_out_up)
{
// each dir is 3 bit wide
_out_forward = (Direction)(_packed >> 3);
_out_up = (Direction)(_packed & 0b111);
}
constexpr uint8_t orientation_2_packed_dirs[24] =
{
// Manually defined orientation
// The default orientation is forward==PosZ with up==PosY
// We then rotate up according to the left-claw-rule (around forward)
// We then rotate forward according to the left-claw-rule (around up)
pack_dirs(Direction::PosZ, Direction::PosY), // 0
pack_dirs(Direction::PosZ, Direction::NegX), // 1
pack_dirs(Direction::PosZ, Direction::NegY), // 2
pack_dirs(Direction::PosZ, Direction::PosX), // 3
pack_dirs(Direction::PosX, Direction::PosY), // 4
pack_dirs(Direction::PosX, Direction::PosZ), // 5
pack_dirs(Direction::PosX, Direction::NegY), // 6
pack_dirs(Direction::PosX, Direction::NegZ), // 7
pack_dirs(Direction::NegZ, Direction::PosY), // 8
pack_dirs(Direction::NegZ, Direction::PosX), // 9
pack_dirs(Direction::NegZ, Direction::NegY), // 10
pack_dirs(Direction::NegZ, Direction::NegX), // 11
pack_dirs(Direction::NegX, Direction::PosY), // 12
pack_dirs(Direction::NegX, Direction::NegZ), // 13
pack_dirs(Direction::NegX, Direction::NegY), // 14
pack_dirs(Direction::NegX, Direction::PosZ), // 15
// For forward==NegY we default to up==PosZ
pack_dirs(Direction::NegY, Direction::PosZ), // 16
pack_dirs(Direction::NegY, Direction::NegX), // 17
pack_dirs(Direction::NegY, Direction::NegZ), // 18
pack_dirs(Direction::NegY, Direction::PosX), // 19
// For forward==PosY we default to up==NegZ
pack_dirs(Direction::PosY, Direction::NegZ), // 20
pack_dirs(Direction::PosY, Direction::NegX), // 21
pack_dirs(Direction::PosY, Direction::PosZ), // 22
pack_dirs(Direction::PosY, Direction::PosX), // 23
};
// Note packed dirs MUST be valid as orientation
// aka dirs must be perpendicular
constexpr uint8_t packed_dirs_2_orientation[44] =
{ // Reverse of ori_to_packed_dirs
UINT8_MAX,
UINT8_MAX,
4,
6,
5,
7,
UINT8_MAX,
UINT8_MAX,
UINT8_MAX,
UINT8_MAX,
12, // 10th element
14,
15,
13,
UINT8_MAX,
UINT8_MAX,
23,
21,
UINT8_MAX,
UINT8_MAX,
22, // 20th element
20,
UINT8_MAX,
UINT8_MAX,
19,
17,
UINT8_MAX,
UINT8_MAX,
16,
18,
UINT8_MAX, // 30th element
UINT8_MAX,
3,
1,
0,
2,
UINT8_MAX,
UINT8_MAX,
UINT8_MAX,
UINT8_MAX,
9, // 40th element
11,
8,
10};
typedef struct orientation
{
uint8_t data;
orientation() : data(packed_dirs_2_orientation[pack_dirs(Direction::PosZ, Direction::PosY)]) {};
~orientation() = default;
constexpr orientation(uint8_t _data) : data(_data) {};
constexpr orientation(const Direction _forward, const Direction _up)
: data(packed_dirs_2_orientation[pack_dirs(_forward, _up)]) {};
constexpr void to_dirs(Direction &_out_forward, Direction &_out_up) const
{
unpack_dirs(orientation_2_packed_dirs[data], _out_forward, _out_up);
}
constexpr void to_matrix(float *_out_mtx)
{
Direction forward;
Direction up;
to_dirs(forward, up);
Vec3 forward_vec = dir_to_normal(forward);
Vec3 up_vec = dir_to_normal(up);
Vec3 right_vec = bx::cross(up_vec, forward_vec);
// default orientation: forward = PosZ & up = PosY
// should result in identity matrix
// => mtx = (right, up, forward)
// Note: GLSL matrices are column major
_out_mtx[0] = right_vec.x == -0.0f ? 0.0f : right_vec.x;
_out_mtx[1] = right_vec.y == -0.0f ? 0.0f : right_vec.y;
_out_mtx[2] = right_vec.z == -0.0f ? 0.0f : right_vec.z;
_out_mtx[3] = up_vec.x == -0.0f ? 0.0f : up_vec.x;
_out_mtx[4] = up_vec.y == -0.0f ? 0.0f : up_vec.y;
_out_mtx[5] = up_vec.z == -0.0f ? 0.0f : up_vec.z;
_out_mtx[6] = forward_vec.x == -0.0f ? 0.0f : forward_vec.x;
_out_mtx[7] = forward_vec.y == -0.0f ? 0.0f : forward_vec.y;
_out_mtx[8] = forward_vec.z == -0.0f ? 0.0f : forward_vec.z;
}
} Orientation;
// Ensure default orientation equals integer 0
constexpr auto check_default_orientation = []() constexpr -> bool
{
Orientation ori(Direction::PosZ, Direction::PosY);
return ori.data == 0;
};
static_assert(check_default_orientation());
// Rotates _ori by 90deg around _axis according to the left-claw-rule
constexpr Orientation rotate(const Orientation _ori, const Direction _axis)
{
Direction forward;
Direction up;
_ori.to_dirs(forward, up);
forward = rotate(forward, _axis);
up = rotate(up, _axis);
return Orientation(forward, up);
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
typedef struct face_normal : public I8Vec3
{
// default constructor, creates invalid face normal
face_normal() : I8Vec3() {};
face_normal(I8Vec3 _v) : I8Vec3(_v.x, _v.y, _v.z)
{
assert((_v.x >= -1 && _v.x <= 1) &&
(_v.y >= -1 && _v.y <= 1) &&
(_v.z >= -1 && _v.z <= 1) &&
!(_v.x == 0 && _v.y == 0 && _v.z == 0));
}
// Parameter MUST be in [-1;1]
// At least one MUST NOT be 0
face_normal(int8_t _x, int8_t _y, int8_t _z) :
I8Vec3(_x, _y, _z)
{
assert((_x >= -1 && _x <= 1) &&
(_y >= -1 && _y <= 1) &&
(_z >= -1 && _z <= 1) &&
!(_x == 0 && _y == 0 && _z == 0));
}
// Convenience constructor
face_normal(Direction _dir) : face_normal(I8Vec3(_dir)) {}
// returns the dimensionality (e.g. dims({0,1,0})==1 ; dims({-1,1,1})==3)
uint8_t dims();
} FaceNormal;
//////////////////////////////////////////////////////////////////////////////////////////////////////
// Work around X.h shenanigan macros
#ifdef None
#define OldNone None
#define PlsRedefineNone 1
#undef None
#endif
namespace component_face_type_ns
{
enum component_face_type : uint8_t
{
// TODO this would be much nicer if the trigs corresponded with
// their comp vertices (apex)
// 0 == BL, 1 == BR, 2 == TL, 3 == TR
None,
Quad,
TrigBL,
TrigBR,
TrigTR,
TrigTL,
Count
};
}
using FaceType = component_face_type_ns::component_face_type;
// Work around X.h shenanigan macros
#ifdef PlsRedefineNone
#define None OldNone
#endif
static_assert(FaceType::None == 0); // necessary for light flooding algorithm
static_assert(FaceType::Quad == 1); // see "flooding_table"
static_assert(FaceType::TrigBL == 2);
static_assert(FaceType::TrigBR == 3);
static_assert(FaceType::TrigTR == 4);
static_assert(FaceType::TrigTL == 5);
static_assert(FaceType::Count == 6);
//////////////////////////////////////////////////////////////////////////////////////////////////////
typedef struct face_edge
{
uint16_t v0;
uint16_t v1;
face_edge() = default;
face_edge(uint16_t _v0, uint16_t _v1) : v0(_v0), v1(_v1) {};
bool operator==(const face_edge &) const = default;
} FaceEdge;
template <>
struct std::hash<FaceEdge>
{
size_t operator()(const FaceEdge &_v) const noexcept
{
uint32_t val = (((uint32_t)_v.v0) << 16) | (((uint32_t)_v.v1));
return std::hash<std::uint32_t>{}(val);
}
};
typedef struct u8_vec3
{
uint8_t x;
uint8_t y;
uint8_t z;
u8_vec3() = default;
u8_vec3(const uint8_t _x, const uint8_t _y, const uint8_t _z) : x(_x), y(_y), z(_z) {};
bool operator==(const u8_vec3 &) const = default;
} U8Vec3;
template <>
struct std::hash<U8Vec3>
{
size_t operator()(const U8Vec3 &_v) const noexcept
{
uint32_t val = (((uint32_t)_v.x) << 16) | (((uint32_t)_v.y) << 8) | (((uint32_t)_v.z));
return std::hash<std::uint32_t>{}(val);
}
};
//////////////////////////////////////////////////////////////////////////////////////////////////////
namespace texture_corner_ns
{
enum corner : uint32_t
{
TopLeft,
CenterLeft,
BotLeft,
CenterBot,
BotRight,
CenterRight,
TopRight,
CenterTop,
Count
};
}
using TexCorner = texture_corner_ns::corner;
// Convenience struct (Basically an array in a trench-coat)
struct ComponentVertices {
uint8_t data[4];
uint8_t& operator[](int _index)
{
return data[_index];
}
const uint8_t& operator[] (int _index) const
{
return data[_index];
}
bool operator==(const ComponentVertices& _o) const
{
return data[0]==_o[0] && data[1]==_o[1] && data[2]==_o[2] && data[3]==_o[3];
}
};
// Convenience struct (Basically an array in a trench-coat)
struct BlockVertices {
uint16_t data[4];
uint16_t& operator[](int _index)
{
return data[_index];
}
const uint16_t& operator[] (int _index) const
{
return data[_index];
}
bool operator==(const BlockVertices& _o) const
{
return data[0]==_o[0] && data[1]==_o[1] && data[2]==_o[2] && data[3]==_o[3];
}
};
// TODO move to world.h?
typedef struct component_face
{
FaceNormal normal;
TexCorner tex_apex; // defines how the triangle/quad samples its texture. In [0;7]
// vertices[1] corresponds with this tex-corners
ComponentVertices vertices; // counter-clockwise winding // vertices[1] MUST be triangle apex // TODO OPT if we find a way to rotate FaceType's we can get rid of these
uint32_t texture_id; // gfx::add_component_textures
bool isValid() const { return vertices[0] != vertices[1]; }
bool isQuad() const { return vertices[3] < 8; }
// Returns the face type as seen in normalized direction, based on the vertices
// Inner faces not supported for now
// Also returns the view direction if non-nullptr
FaceType faceType(Direction* _out_view_dir = nullptr) const;
// Convenience initializer for non-existant/empty faces
static const struct component_face NONE;
} ComponentFace;
constexpr size_t component_face_size = sizeof(ComponentFace);
// A face in block coordinates
// used for block welding
typedef struct block_face
{
const uint16_t INVALID_VERT = 1024; // anything >728 will do, maybe this value can be optimized
uint8_t comp_coords[3]; // coordinates of origination component in block
TexCorner tex_apex;
// block space vertex ids [0;728]
// vert[3] may be any value, but is only valid of isQuad()==true
BlockVertices vertices; // counter-clockwise winding // vertices[1] MUST be triangle apex
FaceType faceType;
FaceNormal normal;
uint32_t comp_texture_id;
bool visited; // for surface walk
bool valid; // for optimization (only valid faces get into the vertex buffer)
uint32_t texture_ids[TexCorner::Count]; // one per TexCorner // for optimization
// Constructor from ComponentFace
explicit block_face(const BlockVertices& block_verts, uint8_t _off_x, uint8_t _off_y, uint8_t _off_z,
TexCorner _tex_apex, FaceType _faceType, FaceNormal _normal, uint32_t _comp_texture_id) :
comp_coords{_off_x, _off_y, _off_z},
tex_apex(_tex_apex), vertices(block_verts),
faceType(_faceType), normal(_normal), comp_texture_id(_comp_texture_id),
visited(false), valid(true), texture_ids{0,0,0,0}
{};
// Default constructor, invalid face
explicit block_face() :
comp_coords{8, 8, 8},
tex_apex(TexCorner::Count),
vertices{INVALID_VERT, INVALID_VERT, INVALID_VERT, INVALID_VERT},
faceType(FaceType::None),
normal(), visited(false), valid(false), texture_ids{0,0,0,0} {};
constexpr bool isQuad() const { return faceType == FaceType::Quad; };
static constexpr uint16_t comp_vertex_2_block_vertex(const uint8_t _v, const uint8_t _off_x, const uint8_t _off_y, const uint8_t _off_z)
{
// place comp in block at origin
// 0 0b000 -> 0
// 1 0b001 -> 1 +1
// 2 0b010 -> 9 +9
// 3 0b011 -> 10 +9+1
// 4 0b100 -> 81 +81
// 5 0b101 -> 82 +81 +1
// 6 0b110 -> 90 +81+9
// 7 0b111 -> 91 +81+9+1
// then shift inside block by offset
return (((_v & 0b001) > 0) + _off_x) +
(((_v & 0b010) > 0) + _off_y) * 9 +
(((_v & 0b100) > 0) + _off_z) * 9 * 9;
}
} BlockFace;
const int BlockFaceSize = sizeof(BlockFace);
//////////////////////////////////////////////////////////////////////////////////////////////////////
// Returns the (up to 4) vertices in [0;8] of the component
// Note: vertex == 8 <=> invalid <=> no vertex
// => vert[4] != 8 <=> FaceType::Quad
// => vert[1] is the apex
ComponentVertices get_component_face_vertices(FaceType _face, Direction _view_dir);
//////////////////////////////////////////////////////////////////////////////////////////////////////
// Returns the angle between _v0 and _v1 around _axis in radians [-pi;pi]
// All input expected to be unit vectors
// Note: Rotation direction is determined by right claw rule
// Returns values in [-pi;pi]
float angle_between(const Vec3 _v0, const Vec3 _v1, const Vec3 _axis);
// Note: rotation follows right claw rule along _common_edge
float angle_between_faces(const BlockFace _f0, const BlockFace _f1, const Vec3 _common_edge);
void transform_mtx(const Vec3 &_position, const Quat &_orientation, float *_out_mtx);
// Inverse ray (used as pre-calculation for collision checks)
inline constexpr InvRay InverseRay(Ray &_ray);
// Check ray <-> AABB collision
bool CheckCollisionRayBox(const InvRay &_ray, const AABB &_box, float &_out_distance);
// Check ray <-> OBB collision
bool CheckCollisionRayOrientedBox(const Ray &_ray, const AABB &_box, const float *_inverse_model_matrix, float &_distance);
void combineAABB(AABB &_a, const AABB &_b);
// Return wether _value lies between _start and _end (both inclusive)
template <typename T>
inline bool between(T _value, T _start, T _end)
{
return _value >= _start && _value <= _end;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
/* Integer Math */
// _result and _vec should be I8Vec3 and _mat should be I8RotMat3x3
void mulMtxVec3(int8_t* _result, const int8_t* _mat, const int8_t* _vec);
// All parameters should be I8RotMat3x3
void mulMtxMtx(int8_t* _result, const int8_t* _a, const int8_t* _b);

1217
src/test.h Normal file

File diff suppressed because it is too large Load Diff

271
src/util.cpp Normal file
View File

@@ -0,0 +1,271 @@
#include "util.h"
#include <cstdio>
#include <cstdarg>
#include <cstring>
#include <assert.h>
#include <cstdlib>
#include <bgfx/bgfx.h>
#include "config.h"
#include "space_math.h"
#include <unordered_set>
#include <unordered_map>
#include "world.h"
#include <atomic>
// TODO logging output should go to a file as well!
void logImpl(FILE *stream, const char *pre, const char *fmt, va_list args)
{
fprintf(stream, "%s", pre);
vfprintf(stream, fmt, args);
}
void logTrace(const char *fmt, ...)
{
if (config::LOG_LEVEL <= config::LOG_TRACE)
{
va_list valist;
va_start(valist, fmt);
logImpl(stderr, "[TRACE] ", fmt, valist);
va_end(valist);
}
}
void logDebug(const char *fmt, ...)
{
if (config::LOG_LEVEL <= config::LOG_DEBUG)
{
va_list valist;
va_start(valist, fmt);
logImpl(stderr, "[DEBUG] ", fmt, valist);
va_end(valist);
}
}
void logInfo(const char *fmt, ...)
{
if (config::LOG_LEVEL <= config::LOG_INFO)
{
va_list valist;
va_start(valist, fmt);
logImpl(stdout, "[INFO] ", fmt, valist);
va_end(valist);
}
}
void logWarn(const char *fmt, ...)
{
if (config::LOG_LEVEL <= config::LOG_WARNING)
{
va_list valist;
va_start(valist, fmt);
logImpl(stderr, "[WARN] ", fmt, valist);
va_end(valist);
}
}
void logErr(const char *fmt, ...)
{
if (config::LOG_LEVEL <= config::LOG_ERROR)
{
va_list valist;
va_start(valist, fmt);
logImpl(stderr, "[ERROR] ", fmt, valist);
va_end(valist);
}
}
[[noreturn]] void DIE(const char *fmt, ...)
{
fflush(stdout); // better safe than sorry
va_list valist;
va_start(valist, fmt);
logImpl(stderr, "[FATAL] ", fmt, valist);
va_end(valist);
fflush(stderr); // better safe than sorry
std::abort(); // abort to show error
}
void generate_vertices(const char *filePath)
{
FILE *file = fopen(filePath, "w+"); // open for writing & truncate if exists
if (!file)
DIE("generateVertices: fopen(%s) failed\n", filePath);
fprintf(file, "const vec3 verts[729] = \n{\n");
// generate vert coordinates
// order: x -> y -> z (chosen arbitrarily..)
const float dist = 1.0f / 8.0f; // normalized distance between vertices
const float off = 0.5f; // offset to center unit cube around (0,0,0)
for (int z = 0; z < 9; z++)
{
float nz = z * dist - off;
for (int y = 0; y < 9; y++)
{
float ny = y * dist - off;
for (int x = 0; x < 9; x++)
{
float nx = x * dist - off;
fprintf(file, "{%f, %f, %f},\n", nx, ny, nz);
}
}
}
fprintf(file, "};\n");
fclose(file);
}
// Generates all 24 block/component orientations as glsl matrices
// Outputs a glsl lookup table (orientation_id -> matrix)
void generate_glsl_orientation_matrices(const char *_filePath)
{
FILE *file = fopen(_filePath, "w+"); // open for writing & truncate if exists
if (!file)
DIE("generateVertices: fopen(%s) failed\n", _filePath);
fprintf(file, "const mat3 orientations[24] = \n{\n");
float mtx[9];
for (uint8_t i = 0; i < 24; i++)
{
Orientation ori(i);
ori.to_matrix(mtx);
fprintf(file, "{{%f, %f, %f}, {%f, %f, %f}, {%f, %f, %f}},\n",
mtx[0], mtx[1], mtx[2],
mtx[3], mtx[4], mtx[5],
mtx[6], mtx[7], mtx[8]);
}
fprintf(file, "};\n");
fclose(file);
}
void find_correct_working_directory()
{
// When debugging we want to change our current working directory
// to the repo root to simulate deployment environment
auto path = std::filesystem::current_path();
// Note: We check for the correct path, by searching for the shaders directory
if (std::filesystem::exists(path / "assets"))
{ // We're already set
return;
}
// Search in upwards
while (path != path.root_path())
{
path = path.parent_path();
if (std::filesystem::exists(path / "assets"))
{
logInfo("Correcting cwd to %s\n", path.string().c_str());
std::filesystem::current_path(path);
return;
}
}
DIE("Could not find correct working directory. I can't find my assets :(\n");
}
bool should_use_wayland()
{
// Check user override
if (getenv("SPACEGAME_USE_WAYLAND"))
{
return true;
}
if (getenv("SPACEGAME_USE_X11"))
{
return false;
}
// Try to detect if wayland is supported
char *xdg_session_type = getenv("XDG_SESSION_TYPE");
if (xdg_session_type == NULL)
{ // Fallback
return getenv("WAYLAND_DISPLAY") != NULL;
}
return std::strcmp(xdg_session_type, "wayland") == 0;
}
// todo add to config?
void startup_checks()
{
uint64_t caps = bgfx::getCaps()->supported;
if (!(BGFX_CAPS_COMPUTE & caps)) // vulkan core 1.0
DIE("compute shaders not supported!\n");
if (!(BGFX_CAPS_DRAW_INDIRECT & caps)) // vulkan core 1.0
DIE("draw indirect not supported!\n");
if (!(BGFX_CAPS_INSTANCING & caps))
DIE("instancing not supported!\n");
if (!(BGFX_CAPS_INDEX32 & caps)) // vulkan core 1.0 (but max value may be limited)
DIE("index32 not supported!\n");
// if (!(BGFX_CAPS_TEXTURE_2D_ARRAY & caps)) // vulkan core 1.0
// DIE("texture2DArrays not supported!\n");
bgfx::Caps::Limits limits = bgfx::getCaps()->limits;
// vulkan property maxDrawIndirectCount
// typically 2^32 -1
// TODO add check
// vulkan property fullDrawIndexUint32 must be supported
// or maxDrawIndexedIndexValue must be 2^32-1
// otherwise 32-bit indices only support values up to 2^24-1
// config::MAX_TEXTURE_LAYERS = limits.maxTextureLayers;
// if (config::MAX_TEXTURE_LAYERS < config::INITIAL_TEXTURE_LAYERS)
// DIE("limit maxTextureLayers is too small (require <=%d; was %d)",
// config::INITIAL_TEXTURE_LAYERS, config::MAX_TEXTURE_LAYERS);
}
// void flag_wait(std::atomic_flag* _flag) {
// std::atomic_flag_wait(_flag, false); // expect flag==false, wait until ==true
// std::atomic_flag_clear(_flag); // reset to ==false
// }
//
// void flag_signal(std::atomic_flag* _flag) {
// while (std::atomic_flag_test_and_set(_flag)) {}; // set flag==true (spin if not yet ==false)
// std::atomic_flag_notify_one(_flag); // wake up potentially waiting thread
// }
// void flag_wait(std::atomic_flag* _flag) {
// std::atomic_flag_wait(_flag, false); // expect flag==false, wait until ==true
// std::atomic_flag_clear(_flag); // reset to ==false
// std::atomic_flag_notify_one(_flag); // wake up potentially wating thread
// }
// void flag_signal(std::atomic_flag* _flag) {
// while (std::atomic_flag_test_and_set(_flag)) { // set flag==true (if still true wait until cleared)
// std::atomic_flag_wait(_flag, true); // expect flag==true, wait until ==false
// };
// std::atomic_flag_notify_one(_flag); // wake up potentially waiting thread
// }
void flag_wait(std::atomic_bool *_flag)
{
std::atomic_wait(_flag, false); // expect flag==false, wait until ==true
_flag->store(false); // reset to ==false
std::atomic_notify_one(_flag); // wake up potentially wating thread
}
void flag_signal(std::atomic_bool *_flag)
{
bool expect = false;
while (!_flag->compare_exchange_weak(expect, true))
{ // set flag==true (if still true wait until cleared)
expect = false; // reset expected value
std::atomic_wait(_flag, true); // expect flag==true, wait until ==false
};
std::atomic_notify_one(_flag); // wake up potentially waiting thread
}

74
src/util.h Normal file
View File

@@ -0,0 +1,74 @@
#pragma once
#include <filesystem>
#include <atomic>
#include <bx/bx.h>
#include <bx/timer.h>
void logTrace(const char *fmt, ...);
void logDebug(const char *fmt, ...);
void logInfo(const char *fmt, ...);
void logWarn(const char *fmt, ...);
void logErr(const char *fmt, ...);
[[noreturn]] void DIE(const char *fmt, ...);
// generate vertex buffer as shader constant
// generate_vertices("C:/Users/Crydsch/Desktop/spacegame v5/shaders/verts.sh");
void generate_vertices(const char *filePath);
void generate_glsl_orientation_matrices(const char *_filePath);
void find_correct_working_directory();
// Returns true if wayland should be used instead of x11
// Only makes sense on *nix systems
bool should_use_wayland();
// performs startup checks ensuring hardware capabilities and integrity
void startup_checks();
// void flag_wait(std::atomic_flag* _flag); TODO maybe re-activate once glibc supports it -.-
// void flag_signal(std::atomic_flag* _flag);
void flag_wait(std::atomic_bool *_flag);
void flag_signal(std::atomic_bool *_flag);
template <typename T>
inline void freeContainer(T &p_container)
{
T empty{};
std::swap(p_container, empty);
};
// Ref.: https://stackoverflow.com/questions/2590677/how-do-i-combine-hash-values-in-c0x
// ref.: https://www.boost.org/doc/libs/1_84_0/libs/container_hash/doc/html/hash.html#combine
// TODO update to this https://www.boost.org/doc/libs/1_86_0/libs/container_hash/doc/html/hash.html#notes_hash_combine
// aka https://github.com/boostorg/container_hash/blob/89e5b98f6bc05841a21069d76cc5adcbee62b9cc/include/boost/container_hash/detail/hash_mix.hpp
// and https://github.com/boostorg/container_hash/blob/89e5b98f6bc05841a21069d76cc5adcbee62b9cc/include/boost/container_hash/hash.hpp#L469
template <class T>
inline void hash_combine(std::size_t& seed, const T& v)
{
std::hash<T> hasher;
seed ^= hasher(v) + 0x9e3779b9 + (seed<<6) + (seed>>2);
}
template <typename T, typename... Rest>
inline void hash_combine(size_t &seed, const T &v, Rest... rest)
{
std::hash<T> hasher;
seed ^= hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
hash_combine(seed, rest...);
}
// use like:
// std::size_t h=0;
// hash_combine(h, obj1, obj2, obj3);
#define BENCH_START \
int64_t bench_time_to_seconds = bx::getHPFrequency(); \
int64_t bench_time_to_millis = bench_time_to_seconds / 1000; \
int64_t bench_time_start = bx::getHPCounter();
#define BENCH_STOP \
int64_t bench_time_stop = bx::getHPCounter(); \
int64_t bench_time_diff = (bench_time_stop - bench_time_start); \
logInfo("BENCHMARK: %f ms\n", ((float)bench_time_diff / (float)bench_time_to_millis));

1070
src/world.cpp Normal file

File diff suppressed because it is too large Load Diff

196
src/world.h Normal file
View File

@@ -0,0 +1,196 @@
#pragma once
#include <vector>
#include <cassert>
#include <unordered_map>
#include <algorithm>
#include <stdint.h>
#include <array>
#include "space_math.h"
// #include "graphics.h"
/* * * * * * * * * * * * * * * * * * * * * * */
/* The world namespace manages all CPU data */
/* * * * * * * * * * * * * * * * * * * * * * */
///
/// All things are referenced per their id.
/// Some ids have special meanings:
/// 0: is invalid for all purposes. Allows efficient initialization.
// Helper functions
// Returns the packed transform of a block (offset in chunk + orientation)
// [off_x | off_y | off_z | orientation]
// TODO should use Orientation struct!
inline uint16_t block_transform_pack(const uint16_t off_x, const uint16_t off_y, const uint16_t off_z, const uint16_t orientation)
{
uint16_t res = 0;
res |= off_x;
res <<= 3;
res |= off_y;
res <<= 3;
res |= off_z;
res <<= 5;
res |= orientation;
return res;
};
// TODO should use Orientation struct!
inline void block_transform_unpack(const uint16_t _transform, uint16_t &_out_off_x, uint16_t &_out_off_y, uint16_t &_out_off_z, uint16_t &_out_orientation)
{
_out_orientation = _transform & 0x1F; // 5 bit
_out_off_z = (_transform >> 5) & 0x7; // 3 bit
_out_off_y = (_transform >> 8) & 0x7; // 3 bit
_out_off_x = (_transform >> 11) & 0x7; // 3 bit
}
inline uint16_t block_transform_to_chunk_index(const uint16_t _transform)
{
return (_transform >> 5); // just remove orientation
}
inline void list_add_sorted(std::vector<uint32_t> &_list, const uint32_t _val)
{
auto it = std::upper_bound(_list.begin(), _list.end(), _val);
assert(it == _list.end() || *it != _val); // value should not yet exist in the list
_list.insert(it, _val);
}
inline void list_remove_sorted(std::vector<uint32_t> &_list, const uint32_t _val)
{
auto it = std::lower_bound(_list.begin(), _list.end(), _val);
assert(it != _list.end()); // value must exist in the list
assert(*it == _val); // value must exist in the list
_list.erase(it);
}
typedef struct grid
{
Vec3 position;
Quat orientation;
std::unordered_map<uint64_t, uint32_t> chunk_ids; // Note: if hashmap too slow try other (ex. emilib/hashmap)
std::vector<uint32_t> chunk_id_list; // kept sorted
AABB aabb;
bool aabb_dirty; // TODO uint_8 state ?
uint32_t gfx_ref; // => GPUGrid
} Grid;
typedef struct chunk
{
uint32_t parent_ref; // => Grid
int16_t parent_offset_x;
int16_t parent_offset_y;
int16_t parent_offset_z;
uint32_t block_ids[512]; // 512 = 8*8*8
// TODO block_list with only valid block_ids ?
uint32_t gfx_ref; // => GPUChunk
} Chunk;
constexpr AABB CHUNK_AABB = {bx::sub(Vec3(0.0f), Vec3(0.5f)), bx::sub(Vec3(8.0f), Vec3(0.5f))}; // A chunk consists of 8*8*8 blocks
typedef struct block
{
/* engine internals */
uint32_t parent_ref; // => Chunk
uint16_t transform; // offset + orientation in parent chunk
uint32_t base_model_ref; // => BlockModel // blueprint OR freshly built
uint32_t curr_model_ref; // => BlockModel // may be ==base_model_id OR damaged variant
uint32_t gfx_ref; // => GPUBlock
/* gameplay attributes */
// uint32_t health; // TODO
} Block;
constexpr AABB BLOCK_AABB = {{-0.5f, -0.5f, -0.5f}, {0.5f, 0.5f, 0.5f}}; // A block has a side length of 1
// instance of a component in a block model
// Note: Components are read-only
// We generate all 24 orientations once during loading. They are fixed from then on.
typedef struct component
{
ComponentFace faces[8];
static Orientation getOrientation(const uint32_t _id);
static uint32_t rotate(const uint32_t _id, const bool _inc);
static uint32_t rotate(const uint32_t _id, const Direction _axis);
} Component;
// Block Model
// What we render for a block
// Depending on its state we might render it differently
// Blueprint => generate & render scaffolding
// During construction => generate & render
// Fully intact => render base model
// Damaged => generate & render "sub"-model
// Note: self glow is encoded in the texture data
// Note: emittance is handled by lighting
typedef struct block_model
{
// ref_counts aka how many blocks refer to this model
uint32_t ram_ref_count; // if ==0 model can be ram unloaded (on cleanup run)
uint32_t gpu_ref_count; // if ==0 model can be gpu unloaded
uint32_t component_ids[512]; // 512 = 8*8*8
uint32_t gfx_ref; // => GPUBlockModel
std::vector<uint32_t> patch_refs; // => GPUTextureAtlasPatch
// TODO free patches on gpu_unload?
uint32_t outline_ref; // => GPULine
uint32_t flags;
static const uint32_t FLAG_HAS_COMPONENTS = 0x1;
// TODO rename to
// has_cpu_data & has_gpu_data
// is_ram_loaded & is_gpu_loaded
bool has_components() { return (flags & FLAG_HAS_COMPONENTS) > 0; }
} BlockModel;
namespace world
{
void init();
// Call every frame! Call BEFORE gfx::update()
void update();
void destroy();
uint32_t add_grid(const Vec3 _position, const Quat _orientation);
void update_grid_position(const uint32_t _id, const Vec3 _new_position);
void update_grid_orientation(const uint32_t _id, const Quat _new_orientation);
void update_grid_transform(const uint32_t _id, const Vec3 _new_position, const Quat _new_orientation);
void remove_grid(const uint32_t _id);
const Grid &get_grid(const uint32_t _id);
const std::vector<uint32_t> &get_grid_list();
uint32_t add_chunk(const uint32_t _parent_grid_id, const int16_t _parent_offset_x, const int16_t _parent_offset_y, const int16_t _parent_offset_z);
void remove_chunk(const uint32_t _id);
const Chunk &get_chunk(const uint32_t _id);
uint32_t add_block(const uint32_t _parent_chunk_id, const uint16_t _transform, const uint32_t _base_model_id, const uint32_t _curr_model_id);
void update_block_curr_model(const uint32_t _id, const uint32_t _curr_model_id);
void rotate_block(const uint32_t _id, const bool _inc);
void rotate_block(const uint32_t _id, const Direction _dir);
void remove_block(const uint32_t _id);
const Block &get_block(const uint32_t _id);
uint32_t add_block_model();
// Note: models are immutable (cannot be changed after setting it once)
void set_block_model_components(const uint32_t _id, const uint32_t _component_ids[512]);
// Note: models are automatically unloaded when no longer referenced by any blocks
const BlockModel &get_block_model(const uint32_t _id);
// The faces are given in Direction order and the last two are internal faces
// e.g. faces[Direction::PosX]
// Faces are interpreted in normalized orientation
// up== PosY when forward== {PosX,NegX,PosZ,NegZ}
// and up== PosZ when forward== {PosY,NegY}
uint32_t add_component(const ComponentFace _faces[8]);
const Component &get_component(const uint32_t _id);
void get_grid_transform_mtx(const uint32_t _id, float *_out_mtx);
void get_chunk_transform_mtx(const uint32_t _id, float *_out_mtx);
void get_block_transform_mtx(uint16_t _block_offset_x, uint16_t _block_offset_y, uint16_t _block_offset_z, uint16_t _block_orientation, const uint32_t _chunk_id, float *_out_mtx);
void get_block_transform_mtx(const uint32_t _id, float *_out_mtx);
Vec3 get_block_world_position(const uint32_t _id);
}