diff --git a/platforms/windows/projects/Client/Client.vcxproj b/platforms/windows/projects/Client/Client.vcxproj index c34a51671..1b5ccb2af 100644 --- a/platforms/windows/projects/Client/Client.vcxproj +++ b/platforms/windows/projects/Client/Client.vcxproj @@ -240,6 +240,7 @@ + @@ -398,6 +399,7 @@ + diff --git a/platforms/windows/projects/Client/Client.vcxproj.filters b/platforms/windows/projects/Client/Client.vcxproj.filters index 86807309a..d7bed0682 100644 --- a/platforms/windows/projects/Client/Client.vcxproj.filters +++ b/platforms/windows/projects/Client/Client.vcxproj.filters @@ -623,6 +623,9 @@ Header Files\App + + Header Files\Renderer + @@ -1093,5 +1096,8 @@ Source Files\GUI\Components + + Source Files\Renderer + \ No newline at end of file diff --git a/platforms/windows/projects/Common/Common.vcxproj b/platforms/windows/projects/Common/Common.vcxproj index a497af7e0..792e4b430 100644 --- a/platforms/windows/projects/Common/Common.vcxproj +++ b/platforms/windows/projects/Common/Common.vcxproj @@ -95,6 +95,7 @@ + @@ -115,6 +116,8 @@ + + diff --git a/platforms/windows/projects/Common/Common.vcxproj.filters b/platforms/windows/projects/Common/Common.vcxproj.filters index 56d68fb15..cea414b7f 100644 --- a/platforms/windows/projects/Common/Common.vcxproj.filters +++ b/platforms/windows/projects/Common/Common.vcxproj.filters @@ -71,6 +71,9 @@ Source Files\Threading + + Source Files + @@ -127,5 +130,11 @@ Header Files\Threading + + Header Files + + + Header Files + \ No newline at end of file diff --git a/platforms/windows/projects/World/World.vcxproj b/platforms/windows/projects/World/World.vcxproj index 8a8312eca..54eb90b0a 100644 --- a/platforms/windows/projects/World/World.vcxproj +++ b/platforms/windows/projects/World/World.vcxproj @@ -228,6 +228,7 @@ + diff --git a/platforms/windows/projects/World/World.vcxproj.filters b/platforms/windows/projects/World/World.vcxproj.filters index 18a19e701..6f8587d9d 100644 --- a/platforms/windows/projects/World/World.vcxproj.filters +++ b/platforms/windows/projects/World/World.vcxproj.filters @@ -659,6 +659,9 @@ Source Files\Item + + Source Files + diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index c41a4aee5..0824e8010 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -12,6 +12,7 @@ add_library(reminecraftpe-core STATIC common/Random.cpp common/SmoothFloat.cpp common/Timer.cpp + common/Stopwatch.cpp common/Util.cpp common/utility/AlignmentHelper.cpp common/utility/JsonParser.cpp @@ -81,6 +82,7 @@ add_library(reminecraftpe-core STATIC client/renderer/FireTexture.cpp client/renderer/FoliageColor.cpp client/renderer/GrassColor.cpp + client/renderer/VisibilityExtimator.cpp client/sound/SoundData.cpp client/sound/SoundPathRepository.cpp client/sound/SoundStream.cpp @@ -424,6 +426,7 @@ add_library(reminecraftpe-core STATIC world/tile/Web.cpp world/tile/FenceTile.cpp world/tile/CraftingTableTile.cpp + world/Facing.cpp renderer/GL/GL.cpp renderer/Attribute.cpp renderer/ConstantBufferMetaData.cpp diff --git a/source/client/renderer/VisibilityExtimator.cpp b/source/client/renderer/VisibilityExtimator.cpp new file mode 100644 index 000000000..f32ef7dc8 --- /dev/null +++ b/source/client/renderer/VisibilityExtimator.cpp @@ -0,0 +1,183 @@ +#include "client/renderer/VisibilityExtimator.hpp" + +VisibilityNode::VisibilityNode(bool empty) +{ + if (empty) + setEmpty(); + else + setOpaque(); +} + +void VisibilityNode::setEmpty() +{ + for (int i = 0; i < Facing::MAX; i++) + { + m_visibility[i].setFull(); + } +} + +void VisibilityNode::setOpaque() +{ + for (int i = 0; i < Facing::MAX; i++) + { + m_visibility[i].setEmpty(); + } +} + +void VisibilityNode::connect(const ByteMask& group) +{ + for (uint8_t i = 0; i < Facing::MAX; i++) + { + if (group.contains(1 << i)) + { + connect(i, group); + } + } +} + +void VisibilityNode::connect(uint8_t A, const ByteMask& connected) +{ + for (uint8_t i = 0; i < Facing::MAX; i++) + { + if (connected.contains(1 << i)) + { + connect(A, i); + } + } +} + +void VisibilityNode::connect(uint8_t A, uint8_t B) +{ + if (A != B) + { + m_visibility[A].add(1 << B); + m_visibility[B].add(1 << A); + } +} + +uint8_t& VisibilityExtimator::_at(const ChunkTilePos& p) +{ + size_t index = p.y + (p.x << 8) + (p.z << 4); + assert(index < TILES_SIZE); + return m_tiles[index]; +} + +uint8_t& VisibilityExtimator::_atWorld(const TilePos& t) +{ + return _at(t - m_origin); +} + +uint8_t* VisibilityExtimator::_at(const ChunkTilePos& pos, ByteMask& set) +{ + if (pos.x > 128) + { + set.add(1 << Facing::WEST); + } + else if (pos.x > 15) + { + set.add(1 << Facing::EAST); + } + else if (pos.y > 128) + { + set.add(1 << Facing::DOWN); + } + else if (pos.y > 15) + { + set.add(1 << Facing::UP); + } + else if (pos.z > 128) + { + set.add(1 << Facing::NORTH); + } + else if (pos.z > 15) + { + set.add(1 << Facing::SOUTH); + } + else + { + return &_at(pos); + } + + return nullptr; +} + +void VisibilityExtimator::_visit(const ChunkTilePos& p, ByteMask& set) +{ + uint8_t* tileState = _at(p, set); + if (tileState && *tileState == TS_EMPTY) + { + m_floodQueue.push_back(p); + } +} + +ByteMask VisibilityExtimator::_floodFill(const ChunkTilePos& startPos) +{ + ByteMask mask; + + m_floodQueue.push_back(startPos); + + while (!m_floodQueue.empty()) + { + const ChunkTilePos& pos = m_floodQueue.front(); + + uint8_t& tileState = _at(pos); + if (tileState == TS_EMPTY) + { + tileState = TS_EMPTY_MARKED; + + for (uint8_t face = 0; face < Facing::MAX; face += 3) + { + ChunkTilePos checkPos = pos + ChunkTilePos(face, face + 1, face + 2); + _visit(checkPos, mask); + } + } + + m_floodQueue.pop_front(); + } + + return mask; +} + +void VisibilityExtimator::setTile(const TilePos& pos, const Tile* t) +{ + if (t->isSolid()) + { + _atWorld(pos) = TS_OPAQUE; + m_emptyTiles--; + } +} + +void VisibilityExtimator::start(const RenderChunk& parent) +{ + m_origin = parent.m_pos; + + memset(m_tiles, TS_EMPTY, TILES_SIZE); + m_emptyTiles = TILES_SIZE; +} + +VisibilityNode VisibilityExtimator::finish() +{ + if (isAllEmpty()) + return VisibilityNode(true); + + VisibilityNode node(false); + + ChunkTilePos p; + for (; p.x <= 15; p.x++) + { + for (; p.z <= 15; p.z++) + { + for (; p.y <= 15; p.y++) + { + if (_at(p) != TS_EMPTY) + continue; + + ByteMask mask = _floodFill(p); + if (mask) + node.connect(mask); + } + } + } + + return node; +} diff --git a/source/client/renderer/VisibilityExtimator.hpp b/source/client/renderer/VisibilityExtimator.hpp new file mode 100644 index 000000000..11243cf11 --- /dev/null +++ b/source/client/renderer/VisibilityExtimator.hpp @@ -0,0 +1,85 @@ +#pragma once + +#include + +#include "common/Stopwatch.hpp" +#include "common/ByteMask.hpp" +#include "world/level/TilePos.hpp" +#include "world/level/levelgen/chunk/ChunkTilePos.hpp" +#include "world/tile/Tile.hpp" +#include "client/renderer/RenderChunk.hpp" + +class VisibilityNode +{ +protected: + ByteMask m_visibility[Facing::MAX]; + +public: + VisibilityNode(bool empty); + void setEmpty(); + void setOpaque(); + void connect(const ByteMask& group); + void connect(uint8_t A, const ByteMask& connected); + void connect(uint8_t A, uint8_t B); + const ByteMask& from(uint8_t facing) const + { + assert(facing < Facing::MAX); + return m_visibility[facing]; + } + bool compare(VisibilityNode& other) const; +}; + +class VisibilityExtimator +{ +protected: + static const int TILES_SIZE = 4096; + static const int TILE_COUNT_EMPTY_THRESHOLD = 3840; + +protected: + enum TileState + { + TS_EMPTY, + TS_OPAQUE, + TS_EMPTY_MARKED + }; + +public: + // TODO + //static ThreadLocal pool; + +public: +#ifdef _DEBUG + Stopwatch m_timer; +#endif +protected: + TilePos m_origin; + int m_emptyTiles; + uint8_t m_tiles[TILES_SIZE]; + std::deque m_floodQueue; + +public: + VisibilityExtimator() + : m_origin(), + m_emptyTiles(0), + m_tiles() + { + } +public: + void start(const RenderChunk& parent); + void setTile(const TilePos& pos, const Tile* t); + bool isAllOpaque() const + { + return m_emptyTiles == 0; + } + bool isAllEmpty() const + { + return m_emptyTiles >= TILE_COUNT_EMPTY_THRESHOLD; + } + VisibilityNode finish(); +protected: + uint8_t* _at(const ChunkTilePos& pos, ByteMask& set); + uint8_t& _at(const ChunkTilePos& p); + uint8_t& _atWorld(const TilePos& t); + void _visit(const ChunkTilePos& p, ByteMask& set); + ByteMask _floodFill(const ChunkTilePos& startPos); +}; diff --git a/source/common/ByteMask.hpp b/source/common/ByteMask.hpp new file mode 100644 index 000000000..4f836f297 --- /dev/null +++ b/source/common/ByteMask.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include + +class ByteMask +{ +protected: + uint8_t mask; + +public: + ByteMask() + : mask(0) + { + } +public: + void setEmpty() + { + mask = 0; + } + void setFull() + { + mask = 0xFF; + } + void add(const uint8_t bit) + { + mask |= bit; + } + bool contains(const uint8_t bit) const + { + return (mask & bit) != 0; + } + operator bool() const + { + return mask != 0; + } + uint8_t toByte() const + { + return mask; + } +}; diff --git a/source/common/Stopwatch.cpp b/source/common/Stopwatch.cpp new file mode 100644 index 000000000..2375b7dcb --- /dev/null +++ b/source/common/Stopwatch.cpp @@ -0,0 +1,94 @@ +#include + +#include "Stopwatch.hpp" +#include "Utils.hpp" +#include "Logger.hpp" + +Stopwatch::Stopwatch() + : m_last(0.0), + m_count(0), + m_printcounter(0) +{ + reset(); +} + +Stopwatch::~Stopwatch() +{ +} + +void Stopwatch::start() +{ + m_st = getTimeS(); +} + +double Stopwatch::stop() +{ + if (isReset()) + return 0.0; + + double time = getTimeS(); + double diff = time - m_st; + + m_last = diff; + if (diff > m_max) + m_max = diff; + + m_count++; + m_st = -1.0; + m_tt += diff; + return m_tt; +} + +double Stopwatch::stopContinue() +{ + if (isReset()) + return 0.0; + + double time = getTimeS(); + double diff = time - m_st; + + m_last = diff; + if (diff > m_max) + m_max = diff; + + m_count++; + m_st = time; + m_tt += diff; + return m_tt; +} + +double Stopwatch::tick() const +{ + if (isReset()) + return 0.0; + + double time = getTimeS(); + double diff = time - m_st; + + return diff; +} + +void Stopwatch::reset() +{ + m_tt = 0.0; + m_max = 0.0; + m_st = -1.0; +} + +void Stopwatch::print(const std::string& prepend) +{ + LOG_I("%s\tTime (AVGms/LTs(MAXs)/TTs, C) : %f/%f(%f)/%f, %d", prepend.c_str(), (m_tt * 1000.0) / m_count, m_last, m_max, m_tt, m_count); +} + +void Stopwatch::printEvery(int n, const std::string& prepend) +{ + if ((m_printcounter + 1) >= n) + { + m_printcounter = 0; + print(prepend); + } + else + { + m_printcounter += 1; + } +} diff --git a/source/common/Stopwatch.hpp b/source/common/Stopwatch.hpp new file mode 100644 index 000000000..9ccea2972 --- /dev/null +++ b/source/common/Stopwatch.hpp @@ -0,0 +1,45 @@ +#pragma once +#include + +class Stopwatch +{ +private: + double m_st; + double m_tt; + double m_last; + double m_max; + int m_count; + int m_printcounter; + +public: + Stopwatch(); + virtual ~Stopwatch(); +public: + void start(); + virtual double stop(); + virtual double stopContinue(); + double getLast() const + { + return m_last; + } + double getTotal() const + { + return m_tt; + } + double getMax() const + { + return m_max; + } + int getCount() const + { + return m_count; + } + double tick() const; + bool isReset() const + { + return m_st == -1.0; + } + void reset(); + void printEvery(int n, const std::string& prepend); + virtual void print(const std::string& prepend); +}; diff --git a/source/world/Facing.cpp b/source/world/Facing.cpp new file mode 100644 index 000000000..b7688893a --- /dev/null +++ b/source/world/Facing.cpp @@ -0,0 +1,10 @@ +#include "world/Facing.hpp" + +Facing::Name Facing::DIRECTIONS[Facing::MAX] = { + Facing::DOWN, + Facing::UP, + Facing::NORTH, + Facing::SOUTH, + Facing::WEST, + Facing::EAST +}; diff --git a/source/world/Facing.hpp b/source/world/Facing.hpp index 740f1ce37..fb2873495 100644 --- a/source/world/Facing.hpp +++ b/source/world/Facing.hpp @@ -10,6 +10,10 @@ class Facing NORTH, // -Z SOUTH, // +Z WEST, // -X - EAST // +X + EAST, // +X + MAX }; + +public: + static Name DIRECTIONS[MAX]; }; diff --git a/source/world/item/RocketItem.cpp b/source/world/item/RocketItem.cpp index 978973136..5e8590d12 100644 --- a/source/world/item/RocketItem.cpp +++ b/source/world/item/RocketItem.cpp @@ -31,6 +31,7 @@ bool RocketItem::useOn(ItemStack* inst, Player* player, Level* level, const Tile case Facing::SOUTH: tp.z++; break; case Facing::WEST: tp.x--; break; case Facing::EAST: tp.x++; break; + default: assert(false); return false; break; } level->addEntity(new Rocket(level, tp + 0.5f)); diff --git a/source/world/item/TileItem.cpp b/source/world/item/TileItem.cpp index 0cf37e19c..45827d41d 100644 --- a/source/world/item/TileItem.cpp +++ b/source/world/item/TileItem.cpp @@ -43,6 +43,7 @@ bool TileItem::useOn(ItemStack* instance, Player* player, Level* level, const Ti case Facing::SOUTH: tp.z++; break; case Facing::WEST: tp.x--; break; case Facing::EAST: tp.x++; break; + default: assert(false); return false; break; } if (instance->m_count == 0) diff --git a/source/world/item/TilePlanterItem.cpp b/source/world/item/TilePlanterItem.cpp index b4c7e6679..34f6b0b05 100644 --- a/source/world/item/TilePlanterItem.cpp +++ b/source/world/item/TilePlanterItem.cpp @@ -31,6 +31,7 @@ bool TilePlanterItem::useOn(ItemStack* instance, Player* player, Level* level, c case Facing::SOUTH: tp.z++; break; case Facing::WEST: tp.x--; break; case Facing::EAST: tp.x++; break; + default: assert(false); return false; break; } if (!instance->m_count) diff --git a/source/world/level/levelgen/chunk/ChunkTilePos.hpp b/source/world/level/levelgen/chunk/ChunkTilePos.hpp index 923a430f3..ea7ff510e 100644 --- a/source/world/level/levelgen/chunk/ChunkTilePos.hpp +++ b/source/world/level/levelgen/chunk/ChunkTilePos.hpp @@ -15,6 +15,11 @@ struct ChunkTilePos ChunkTilePos(uint8_t _x, uint8_t _y, uint8_t _z) { _init(_x, _y, _z); } ChunkTilePos(const TilePos& pos) { _init(pos.x & 0xF, pos.y, pos.z & 0xF); } // & 0xF on x and y to get them to uint8_t + ChunkTilePos operator+(const ChunkTilePos& other) const + { + return ChunkTilePos(x + other.x, y + other.y, z + other.z); + } + ChunkTilePos operator+(const TilePos& other) const { return ChunkTilePos(x + other.x, y + other.y, z + other.z); diff --git a/source/world/tile/Tile.cpp b/source/world/tile/Tile.cpp index 7c5646745..95dfc8318 100644 --- a/source/world/tile/Tile.cpp +++ b/source/world/tile/Tile.cpp @@ -909,6 +909,9 @@ bool Tile::shouldRenderFace(const LevelSource* pSrc, const TilePos& pos, Facing: case Facing::UP: if (m_aabb.max.y < 1.0f) return true; break; + default: + assert(false); + return false; } Tile* pTile = Tile::tiles[pSrc->getTile(pos)]; diff --git a/source/world/tile/Tile.hpp b/source/world/tile/Tile.hpp index aad6af4c2..312466da9 100644 --- a/source/world/tile/Tile.hpp +++ b/source/world/tile/Tile.hpp @@ -139,6 +139,11 @@ class Tile bool containsY(const Vec3&); bool containsZ(const Vec3&); + bool isSolid() const + { + return solid[m_ID]; + } + public: // static functions static void initTiles(); static void teardownTiles(); diff --git a/source/world/tile/TorchTile.cpp b/source/world/tile/TorchTile.cpp index c423097e9..ebb26c454 100644 --- a/source/world/tile/TorchTile.cpp +++ b/source/world/tile/TorchTile.cpp @@ -183,6 +183,9 @@ void TorchTile::setPlacedOnFace(Level* level, const TilePos& pos, Facing::Name f break; case Facing::DOWN: break; + default: + assert(false); + return; } level->setData(pos, data);