diff options
author | 2019-10-13 13:50:38 +0200 | |
---|---|---|
committer | 2019-10-13 07:50:38 -0400 | |
commit | 0837e7451a84f95c29dbdb9bd6b8b931fee1635d (patch) | |
tree | 626bcbd250a7fbebdf5958a288d2f438d4f9fb5a /3rdparty/bgfx/examples/40-svt/vt.cpp | |
parent | c913ccb59d713ce0b91135520719ac5385e54358 (diff) |
WIP: sync bgfx, bx and bimg with latest upstream (#5723)
* Sync with bgfx upstream revision b91d0b6
* Sync with bx upstream revision d60912b
* Sync with bimg upstream revision bd81f60
* Add astc-codec decoder
* Rename VertexDecl to VertexLayout
* Rename UniformType enum Int1 to Sampler.
* Add NVN stub
* Fix unused-const-variable error on macOS
* Drop redundant explicit language parameters
buildoptions_cpp are only applied to c++ files and buildoptions_objcpp are only
applied to objective c++ files. As such, hardcoding -x offers no benefit while
preventing overrides (such as one needed by 3rdparty/bgfx/src/renderer_vk.cpp on
macOS) from working.
* Re-introduce -x c++ in places where C code is compiled as C++ to prevent clang from throwing a warning
* Build bgfx as Objective-C++ on macOS
It is needed due to included headers
* Enable Direct3D12 and Vulkan bgfx rendering backends
* Enable building of spirv shaders
* Properly escape /c in cmd call
* Comment out dx12 bgfx renderer
* Honor VERBOSE setting during shaders build
* Only invert hlsl shader XYZ_TO_sRGB matrix for opengl
* Add spirv shaders
* OpenGL ES needs transposed matrix too
* Metal needs transposed matrix as well
Diffstat (limited to '3rdparty/bgfx/examples/40-svt/vt.cpp')
-rw-r--r-- | 3rdparty/bgfx/examples/40-svt/vt.cpp | 1302 |
1 files changed, 1302 insertions, 0 deletions
diff --git a/3rdparty/bgfx/examples/40-svt/vt.cpp b/3rdparty/bgfx/examples/40-svt/vt.cpp new file mode 100644 index 00000000000..9865af4d351 --- /dev/null +++ b/3rdparty/bgfx/examples/40-svt/vt.cpp @@ -0,0 +1,1302 @@ +/* + * Copyright 2018 Ales Mlakar. All rights reserved. + * License: https://github.com/bkaradzic/bgfx#license-bsd-2-clause + */ + + /* + * Reference(s): + * - Sparse Virtual Textures by Sean Barrett + * http://web.archive.org/web/20190103162611/http://silverspaceship.com/src/svt/ + * - Based on Virtual Texture Demo by Brad Blanchard + * http://web.archive.org/web/20190103162638/http://linedef.com/virtual-texture-demo.html + * - Mars texture + * http://web.archive.org/web/20190103162730/http://www.celestiamotherlode.net/catalog/mars.php + */ + +#include <bx/file.h> +#include <bx/sort.h> + +#include "vt.h" + +namespace vt +{ + +// Constants +static const int s_channelCount = 4; +static const int s_tileFileDataOffset = sizeof(VirtualTextureInfo); + +// Page +Page::operator size_t() const { + return size_t((uint32_t(m_mip) << 16) | uint32_t((uint16_t(m_x) << 8) | uint16_t(m_y))); +} + +// PageCount +PageCount::PageCount(Page _page, int _count) + : m_page(_page) + , m_count(_count) +{ +} + +int PageCount::compareTo(const PageCount& other) const +{ + #define Comparer(a, b) bx::clamp<int>(a - b, -1, 1) + if (other.m_page.m_mip != m_page.m_mip) + { + return Comparer(other.m_page.m_mip, m_page.m_mip); + } + return Comparer(other.m_count, m_count); + #undef Comparer +} + +// VirtualTextureInfo +VirtualTextureInfo::VirtualTextureInfo() + : m_virtualTextureSize(0) + , m_tileSize(0) + , m_borderSize(0) +{ +} +int VirtualTextureInfo::GetPageSize() const +{ + return m_tileSize + 2 * m_borderSize; +} + +int VirtualTextureInfo::GetPageTableSize() const +{ + return m_virtualTextureSize / m_tileSize; +} + +// StagingPool +StagingPool::StagingPool(int _width, int _height, int _count, bool _readBack) + : m_stagingTextureIndex(0) + , m_width(_width) + , m_height(_height) + , m_flags(0) +{ + m_flags = BGFX_TEXTURE_BLIT_DST | BGFX_SAMPLER_UVW_CLAMP; + if (_readBack) + { + m_flags |= BGFX_TEXTURE_READ_BACK; + } + grow(_count); +} + +StagingPool::~StagingPool() +{ + for (int i = 0; i < (int)m_stagingTextures.size(); ++i) + { + bgfx::destroy(m_stagingTextures[i]); + } +} + +void StagingPool::grow(int count) +{ + while ((int)m_stagingTextures.size() < count) + { + auto stagingTexture = bgfx::createTexture2D((uint16_t)m_width, (uint16_t)m_height, false, 1, bgfx::TextureFormat::BGRA8, m_flags); + m_stagingTextures.push_back(stagingTexture); + } +} + +bgfx::TextureHandle StagingPool::getTexture() +{ + return m_stagingTextures[m_stagingTextureIndex]; +} + +void StagingPool::next() +{ + m_stagingTextureIndex = (m_stagingTextureIndex + 1) % (int)m_stagingTextures.size(); +} + +// PageIndexer +PageIndexer::PageIndexer(VirtualTextureInfo* _info) + : m_info(_info) +{ + m_mipcount = int(bx::log2((float)m_info->GetPageTableSize()) + 1); + + m_sizes.resize(m_mipcount); + for (int i = 0; i < m_mipcount; ++i) + { + m_sizes[i] = (m_info->m_virtualTextureSize / m_info->m_tileSize) >> i; + } + + m_offsets.resize(m_mipcount); + m_count = 0; + for (int i = 0; i < m_mipcount; ++i) + { + m_offsets[i] = m_count; + m_count += m_sizes[i] * m_sizes[i]; + } + + // Calculate reverse mapping + m_reverse.resize(m_count); + for (int i = 0; i < m_mipcount; ++i) + { + int size = m_sizes[i]; + for (int y = 0; y < size; ++y) + { + for (int x = 0; x < size; ++x) + { + Page page = { x, y, i }; + m_reverse[getIndexFromPage(page)] = page; + } + } + } +} + +int PageIndexer::getIndexFromPage(Page page) +{ + int offset = m_offsets[page.m_mip]; + int stride = m_sizes[page.m_mip]; + + return offset + page.m_y * stride + page.m_x; +} + +Page PageIndexer::getPageFromIndex(int index) +{ + return m_reverse[index]; +} + +bool PageIndexer::isValid(Page page) +{ + if (page.m_mip < 0) + { + return false; + } + + else if (page.m_mip >= m_mipcount) + { + return false; + } + + if (page.m_x < 0) + { + return false; + } + else if (page.m_x >= m_sizes[page.m_mip]) + { + return false; + } + + if (page.m_y < 0) + { + return false; + } + else if (page.m_y >= m_sizes[page.m_mip]) + { + return false; + } + + return true; +} + +int PageIndexer::getCount() const +{ + return m_count; +} + +int PageIndexer::getMipCount() const { + return m_mipcount; +} + +// SimpleImage +SimpleImage::SimpleImage(int _width, int _height, int _channelCount, uint8_t _clearValue) + : m_width(_width) + , m_height(_height) + , m_channelCount(_channelCount) +{ + m_data.resize(m_width * m_height * m_channelCount); + clear(_clearValue); +} + +SimpleImage::SimpleImage(int _width, int _height, int _channelCount, tinystl::vector<uint8_t>& _data) + : m_width(_width) + , m_height(_height) + , m_channelCount(_channelCount) +{ + m_data = _data; +} + +void SimpleImage::copy(Point dest_offset, SimpleImage& src, Rect src_rect) +{ + int width = bx::min(m_width - dest_offset.m_x, src_rect.m_width); + int height = bx::min(m_height - dest_offset.m_y, src_rect.m_height); + int channels = bx::min(m_channelCount, src.m_channelCount); + + for (int j = 0; j < height; ++j) + { + for (int i = 0; i < width; ++i) + { + int i1 = ((j + dest_offset.m_y) * m_width + (i + dest_offset.m_x)) * m_channelCount; + int i2 = ((j + src_rect.m_y) * src.m_width + (i + src_rect.m_x)) * src.m_channelCount; + for (int c = 0; c < channels; ++c) + { + m_data[i1 + c] = src.m_data[i2 + c]; + } + } + } +} + +void SimpleImage::clear(uint8_t clearValue) +{ + bx::memSet(&m_data[0], clearValue, m_width * m_height * m_channelCount); +} + +void SimpleImage::fill(Rect rect, uint8_t r, uint8_t g, uint8_t b, uint8_t a) +{ + for (int y = rect.minY(); y < rect.maxY(); ++y) + { + for (int x = rect.minX(); x < rect.maxX(); ++x) + { + m_data[m_channelCount * (y * m_width + x) + 0] = b; + m_data[m_channelCount * (y * m_width + x) + 1] = g; + m_data[m_channelCount * (y * m_width + x) + 2] = r; + m_data[m_channelCount * (y * m_width + x) + 3] = a; + } + } +} + +void SimpleImage::mipmap(uint8_t* source, int size, int channels, uint8_t* dest) +{ + int mipsize = size / 2; + + for (int y = 0; y < mipsize; ++y) + { + for (int x = 0; x < mipsize; ++x) + { + for (int c = 0; c < channels; ++c) + { + int index = channels * ((y * 2) * size + (x * 2)) + c; + int sum_value = 4 >> 1; + sum_value += source[index + channels * (0 * size + 0)]; + sum_value += source[index + channels * (0 * size + 1)]; + sum_value += source[index + channels * (1 * size + 0)]; + sum_value += source[index + channels * (1 * size + 1)]; + dest[channels * (y * mipsize + x) + c] = (uint8_t)(sum_value / 4); + } + } + } +} + +// Quadtree +Quadtree::Quadtree(Rect _rect, int _level) + : m_rectangle(_rect) + , m_level(_level) +{ + for (int i = 0; i < 4; ++i) + { + m_children[i] = nullptr; + } +} + +Quadtree::~Quadtree() +{ + for (int i = 0; i < 4; ++i) + { + if (m_children[i] != nullptr) + { + BX_DELETE(VirtualTexture::getAllocator(), m_children[i]); + } + } +} + +void Quadtree::add(Page request, Point mapping) +{ + int scale = 1 << request.m_mip; // Same as pow( 2, mip ) + int x = request.m_x * scale; + int y = request.m_y * scale; + + Quadtree* node = this; + + while (request.m_mip < node->m_level) + { + for (int i = 0; i < 4; ++i) + { + auto rect = node->getRectangle(i); + if (rect.contains({ x, y })) + { + // Create a new one if needed + if (node->m_children[i] == nullptr) + { + node->m_children[i] = BX_NEW(VirtualTexture::getAllocator(), Quadtree)(rect, node->m_level - 1); + node = node->m_children[i]; + break; + } + // Otherwise traverse the tree + else + { + node = node->m_children[i]; + break; + } + } + } + } + + // We have created the correct node, now set the mapping + node->m_mapping = mapping; +} + +void Quadtree::remove(Page request) +{ + int index; + auto node = findPage(this, request, &index); + + if (node != nullptr) + { + BX_DELETE(VirtualTexture::getAllocator(), node->m_children[index]); + node->m_children[index] = nullptr; + } +} + +void Quadtree::write(SimpleImage& image, int miplevel) +{ + write(this, image, miplevel); +} + +// Static Functions +Rect Quadtree::getRectangle(int index) +{ + int x = m_rectangle.m_x; + int y = m_rectangle.m_y; + int w = m_rectangle.m_width / 2; + int h = m_rectangle.m_width / 2; + + switch (index) + { + case 0: + return { x, y, w, h }; + case 1: + return { x + w, y, w, h }; + case 2: + return { x + w, y + h, w, h }; + case 3: + return { x, y + h, w, h }; + } + return { 0,0,0,0 }; +} + +void Quadtree::write(Quadtree* node, SimpleImage& image, int miplevel) +{ + if (node->m_level >= miplevel) + { + int rx = node->m_rectangle.m_x >> miplevel; + int ry = node->m_rectangle.m_y >> miplevel; + int rw = node->m_rectangle.m_width >> miplevel; + int rh = node->m_rectangle.m_width >> miplevel; + + image.fill({ rx, ry, rw, rh }, (uint8_t)node->m_mapping.m_x, (uint8_t)node->m_mapping.m_y, (uint8_t)node->m_level, 255); + + for (int i = 0; i < 4; ++i) + { + auto child = node->m_children[i]; + if (child != nullptr) + { + Quadtree::write(child, image, miplevel); + } + } + } +} + +Quadtree* Quadtree::findPage(Quadtree* node, Page request, int* index) +{ + int scale = 1 << request.m_mip; // Same as pow( 2, mip ) + int x = request.m_x * scale; + int y = request.m_y * scale; + + // Find the parent of the child we want to remove + bool exitloop = false; + while (!exitloop) + { + exitloop = true; + for (int i = 0; i < 4; ++i) + { + if (node->m_children[i] != nullptr && node->m_children[i]->m_rectangle.contains({ x, y })) + { + // We found it + if (request.m_mip == node->m_level - 1) + { + *index = i; + return node; + } + // Check the children + else + { + node = node->m_children[i]; + exitloop = false; + } + } + } + } + + // We couldn't find it so it must not exist anymore + *index = -1; + return nullptr; +} + +// PageTable +PageTable::PageTable(PageCache* _cache, VirtualTextureInfo* _info, PageIndexer* _indexer) + : m_info(_info) + , m_indexer(_indexer) + , m_quadtree(nullptr) + , m_quadtreeDirty(true) // Force quadtree dirty on startup +{ + auto size = m_info->GetPageTableSize(); + m_quadtree = BX_NEW(VirtualTexture::getAllocator(), Quadtree)({ 0, 0, size, size }, (int)bx::log2((float)size)); + m_texture = bgfx::createTexture2D((uint16_t)size, (uint16_t)size, true, 1, bgfx::TextureFormat::BGRA8, BGFX_SAMPLER_UVW_CLAMP | BGFX_SAMPLER_POINT); + + _cache->added = [=](Page page, Point pt) { m_quadtreeDirty = true; m_quadtree->add(page, pt); }; + _cache->removed = [=](Page page, Point pt) { m_quadtreeDirty = true; m_quadtree->remove(page); BX_UNUSED(pt); }; + + auto PageTableSizeLog2 = m_indexer->getMipCount(); + + for (int i = 0; i < PageTableSizeLog2; ++i) + { + int mipSize = m_info->GetPageTableSize() >> i; + auto simpleImage = BX_NEW(VirtualTexture::getAllocator(), SimpleImage)(mipSize, mipSize, s_channelCount); + auto stagingTexture = bgfx::createTexture2D((uint16_t)mipSize, (uint16_t)mipSize, false, 1, bgfx::TextureFormat::BGRA8, BGFX_SAMPLER_UVW_CLAMP | BGFX_SAMPLER_POINT); + m_images.push_back(simpleImage); + m_stagingTextures.push_back(stagingTexture); + } +} + +PageTable::~PageTable() +{ + BX_DELETE(VirtualTexture::getAllocator(), m_quadtree); + bgfx::destroy(m_texture); + for (int i = 0; i < (int)m_images.size(); ++i) + { + BX_DELETE(VirtualTexture::getAllocator(), m_images[i]); + } + for (int i = 0; i < (int)m_stagingTextures.size(); ++i) + { + bgfx::destroy(m_stagingTextures[i]); + } +} + +void PageTable::update(bgfx::ViewId blitViewId) +{ + if (!m_quadtreeDirty) + { + return; + } + m_quadtreeDirty = false; + auto PageTableSizeLog2 = m_indexer->getMipCount(); + for (int i = 0; i < PageTableSizeLog2; ++i) + { + m_quadtree->write(*m_images[i], i); + auto stagingTexture = m_stagingTextures[i]; + auto size = uint16_t(m_info->GetPageTableSize() >> i); + bgfx::updateTexture2D(stagingTexture, 0, 0, 0, 0, size, size, bgfx::copy(&m_images[i]->m_data[0], size * size * s_channelCount)); + bgfx::blit(blitViewId, m_texture, uint8_t(i), 0, 0, 0, stagingTexture, 0, 0, 0, 0, size, size); + } +} + +bgfx::TextureHandle PageTable::getTexture() +{ + return m_texture; +} + +// PageLoader +PageLoader::PageLoader(TileDataFile* _tileDataFile, PageIndexer* _indexer, VirtualTextureInfo* _info) + : m_colorMipLevels(false) + , m_showBorders(false) + , m_tileDataFile(_tileDataFile) + , m_indexer(_indexer) + , m_info(_info) +{ +} + +void PageLoader::submit(Page request) +{ + ReadState state; + state.m_page = request; + loadPage(state); + onPageLoadComplete(state); +} + +void PageLoader::loadPage(ReadState& state) +{ + int size = m_info->GetPageSize() * m_info->GetPageSize() * s_channelCount; + state.m_data.resize(size); + if (m_colorMipLevels) + { + copyColor(&state.m_data[0], state.m_page); + } + else if (m_tileDataFile != nullptr) + { + m_tileDataFile->readPage(m_indexer->getIndexFromPage(state.m_page), &state.m_data[0]); + } + if (m_showBorders) + { + copyBorder(&state.m_data[0]); + } +} + +void PageLoader::onPageLoadComplete(ReadState& state) +{ + loadComplete(state.m_page, &state.m_data[0]); +} + +void PageLoader::copyBorder(uint8_t* image) +{ + int pagesize = m_info->GetPageSize(); + int bordersize = m_info->m_borderSize; + + for (int i = 0; i < pagesize; ++i) + { + int xindex = bordersize * pagesize + i; + image[xindex * s_channelCount + 0] = 0; + image[xindex * s_channelCount + 1] = 255; + image[xindex * s_channelCount + 2] = 0; + image[xindex * s_channelCount + 3] = 255; + + int yindex = i * pagesize + bordersize; + image[yindex * s_channelCount + 0] = 0; + image[yindex * s_channelCount + 1] = 255; + image[yindex * s_channelCount + 2] = 0; + image[yindex * s_channelCount + 3] = 255; + } +} + +void PageLoader::copyColor(uint8_t* image, Page request) +{ + static const Color colors[] = + { + {0, 0, 255, 255}, + {0, 255, 255, 255}, + {255, 0, 0, 255}, + {255, 0, 255, 255}, + {255, 255, 0, 255}, + {64, 64, 192, 255}, + {64, 192, 64, 255}, + {64, 192, 192, 255}, + {192, 64, 64, 255}, + {192, 64, 192, 255}, + {192, 192, 64, 255}, + {0, 255, 0, 255} + }; + + int pagesize = m_info->GetPageSize(); + + for (int y = 0; y < pagesize; ++y) + { + for (int x = 0; x < pagesize; ++x) + { + image[(y * pagesize + x) * s_channelCount + 0] = colors[request.m_mip].m_b; + image[(y * pagesize + x) * s_channelCount + 1] = colors[request.m_mip].m_g; + image[(y * pagesize + x) * s_channelCount + 2] = colors[request.m_mip].m_r; + image[(y * pagesize + x) * s_channelCount + 3] = colors[request.m_mip].m_a; + } + } +} + +PageCache::PageCache(TextureAtlas* _atlas, PageLoader* _loader, int _count) + : m_atlas(_atlas) + , m_loader(_loader) + , m_count(_count) +{ + clear(); + m_loader->loadComplete = [&](Page page, uint8_t* data) { loadComplete(page, data); }; +} + +// Update the pages's position in the lru +bool PageCache::touch(Page page) +{ + if (m_loading.find(page) == m_loading.end()) + { + if (m_lru_used.find(page) != m_lru_used.end()) + { + // Find the page (slow!!) and add it to the back of the list + for (auto it = m_lru.begin(); it != m_lru.end(); ++it) + { + if (it->m_page == page) + { + auto lruPage = *it; + m_lru.erase(it); + m_lru.push_back(lruPage); + return true; + } + } + return false; + } + } + + return false; +} + +// Schedule a load if not already loaded or loading +bool PageCache::request(Page request, bgfx::ViewId blitViewId) +{ + m_blitViewId = blitViewId; + if (m_loading.find(request) == m_loading.end()) + { + if (m_lru_used.find(request) == m_lru_used.end()) + { + m_loading.insert(request); + m_loader->submit(request); + return true; + } + } + + return false; +} + +void PageCache::clear() +{ + for (auto& lru_page : m_lru) + { + if (m_lru_used.find(lru_page.m_page) != m_lru_used.end()) + { + removed(lru_page.m_page, lru_page.m_point); + } + } + m_lru_used.clear(); + m_lru.clear(); + m_lru.reserve(m_count * m_count); + m_current = 0; +} + +void PageCache::loadComplete(Page page, uint8_t* data) +{ + m_loading.erase(page); + + // Find a place in the atlas for the data + Point pt; + + if (m_current == m_count * m_count) + { + // Remove the oldest lru page and remember it's location so we can use it + auto lru_page = m_lru[0]; + m_lru.erase(m_lru.begin()); + m_lru_used.erase(lru_page.m_page); + pt = lru_page.m_point; + // Notify that we removed a page + removed(lru_page.m_page, lru_page.m_point); + } + else + { + pt = { m_current % m_count, m_current / m_count }; + ++m_current; + + if (m_current == m_count * m_count) + { + bx::debugPrintf("Atlas is full!"); + } + } + + // Notify atlas that he can upload the page and add the page to lru + m_atlas->uploadPage(pt, data, m_blitViewId); + m_lru.push_back({ page, pt }); + m_lru_used.insert(page); + + // Signal that we added a page + added(page, pt); +} + +// TextureAtlas +TextureAtlas::TextureAtlas(VirtualTextureInfo* _info, int _count, int _uploadsperframe) + : m_info(_info) + , m_stagingPool(_info->GetPageSize(), _info->GetPageSize(), _uploadsperframe, false) +{ + // Create atlas texture + int pagesize = m_info->GetPageSize(); + int size = _count * pagesize; + m_texture = bgfx::createTexture2D((uint16_t)size, (uint16_t)size, false, 1, bgfx::TextureFormat::BGRA8, BGFX_SAMPLER_UVW_CLAMP); +} + +TextureAtlas::~TextureAtlas() +{ + bgfx::destroy(m_texture); +} + +void TextureAtlas::setUploadsPerFrame(int count) +{ + m_stagingPool.grow(count); +} + +void TextureAtlas::uploadPage(Point pt, uint8_t* data, bgfx::ViewId blitViewId) +{ + // Get next staging texture to write to + auto writer = m_stagingPool.getTexture(); + m_stagingPool.next(); + + // Update texture with new atlas data + auto pagesize = uint16_t(m_info->GetPageSize()); + bgfx::updateTexture2D(writer, 0, 0, 0, 0, pagesize, pagesize, bgfx::copy(data, pagesize * pagesize * s_channelCount)); + + // Copy the texture part to the actual atlas texture + auto xpos = uint16_t(pt.m_x * pagesize); + auto ypos = uint16_t(pt.m_y * pagesize); + bgfx::blit(blitViewId, m_texture, 0, xpos, ypos, 0, writer, 0, 0, 0, 0, pagesize, pagesize); +} + +bgfx::TextureHandle TextureAtlas::getTexture() +{ + return m_texture; +} + +// FeedbackBuffer +FeedbackBuffer::FeedbackBuffer(VirtualTextureInfo* _info, int _width, int _height) + : m_info(_info) + , m_width(_width) + , m_height(_height) + , m_stagingPool(_width, _height, 1, true) +{ + // Setup classes + m_indexer = BX_NEW(VirtualTexture::getAllocator(), PageIndexer)(m_info); + m_requests.resize(m_indexer->getCount()); + // Initialize and clear buffers + m_downloadBuffer.resize(m_width * m_height * s_channelCount); + bx::memSet(&m_downloadBuffer[0], 0, m_width * m_height * s_channelCount); + clear(); + // Initialize feedback frame buffer + bgfx::TextureHandle feedbackFrameBufferTextures[] = + { + bgfx::createTexture2D(uint16_t(m_width), uint16_t(m_height), false, 1, bgfx::TextureFormat::BGRA8, BGFX_TEXTURE_RT), + bgfx::createTexture2D(uint16_t(m_width), uint16_t(m_height), false, 1, bgfx::TextureFormat::D24S8, BGFX_TEXTURE_RT), + }; + m_feedbackFrameBuffer = bgfx::createFrameBuffer(BX_COUNTOF(feedbackFrameBufferTextures), feedbackFrameBufferTextures, true); + m_lastStagingTexture = { bgfx::kInvalidHandle }; +} + +FeedbackBuffer::~FeedbackBuffer() +{ + BX_DELETE(VirtualTexture::getAllocator(), m_indexer); + bgfx::destroy(m_feedbackFrameBuffer); +} + +void FeedbackBuffer::clear() +{ + // Clear Table + bx::memSet(&m_requests[0], 0, sizeof(int) * m_indexer->getCount()); +} + +void FeedbackBuffer::copy(bgfx::ViewId viewId) +{ + m_lastStagingTexture = m_stagingPool.getTexture(); + // Copy feedback buffer render target to staging texture + bgfx::blit(viewId, m_lastStagingTexture, 0, 0, bgfx::getTexture(m_feedbackFrameBuffer)); + m_stagingPool.next(); +} + +void FeedbackBuffer::download() +{ + // Check if there's an already rendered feedback buffer available + if (m_lastStagingTexture.idx == bgfx::kInvalidHandle) + { + return; + } + // Read the texture + bgfx::readTexture(m_lastStagingTexture, &m_downloadBuffer[0]); + // Loop through pixels and check if anything was written + auto data = &m_downloadBuffer[0]; + auto colors = (Color*)data; + auto dataSize = m_width * m_height; + for (int i = 0; i < dataSize; ++i) + { + auto& color = colors[i]; + if (color.m_a >= 0xff) + { + // Page found! Add it to the request queue + Page request = { color.m_b, color.m_g, color.m_r }; + addRequestAndParents(request); + // Clear the pixel, so that we don't have to do it in another pass + color = { 0,0,0,0 }; + } + } +} + +// This function validates the pages and adds the page's parents +// We do this so that we can fall back to them if we run out of memory +void FeedbackBuffer::addRequestAndParents(Page request) +{ + auto PageTableSizeLog2 = m_indexer->getMipCount(); + auto count = PageTableSizeLog2 - request.m_mip; + + for (int i = 0; i < count; ++i) + { + int xpos = request.m_x >> i; + int ypos = request.m_y >> i; + + Page page = { xpos, ypos, request.m_mip + i }; + + // If it's not a valid page (position or mip out of range) just skip it + if (!m_indexer->isValid(page)) + { + return; + } + + ++m_requests[m_indexer->getIndexFromPage(page)]; + } +} + +const tinystl::vector<int>& FeedbackBuffer::getRequests() const +{ + return m_requests; +} + +bgfx::FrameBufferHandle FeedbackBuffer::getFrameBuffer() +{ + return m_feedbackFrameBuffer; +} + +int FeedbackBuffer::getWidth() const +{ + return m_width; +} + +int FeedbackBuffer::getHeight() const +{ + return m_height; +} + +// VirtualTexture +VirtualTexture::VirtualTexture(TileDataFile* _tileDataFile, VirtualTextureInfo* _info, int _atlassize, int _uploadsperframe, int _mipBias) + : m_tileDataFile(_tileDataFile) + , m_info(_info) + , m_uploadsPerFrame(_uploadsperframe) + , m_mipBias(_mipBias) + +{ + m_atlasCount = _atlassize / m_info->GetPageSize(); + + // Setup indexer + m_indexer = BX_NEW(VirtualTexture::getAllocator(), PageIndexer)(m_info); + m_pagesToLoad.reserve(m_indexer->getCount()); + + // Setup classes + m_atlas = BX_NEW(VirtualTexture::getAllocator(), TextureAtlas)(m_info, m_atlasCount, m_uploadsPerFrame); + m_loader = BX_NEW(VirtualTexture::getAllocator(), PageLoader)(m_tileDataFile, m_indexer, m_info); + m_cache = BX_NEW(VirtualTexture::getAllocator(), PageCache)(m_atlas, m_loader, m_atlasCount); + m_pageTable = BX_NEW(VirtualTexture::getAllocator(), PageTable)(m_cache, m_info, m_indexer); + + // Create uniforms + u_vt_settings_1 = bgfx::createUniform("u_vt_settings_1", bgfx::UniformType::Vec4); + u_vt_settings_2 = bgfx::createUniform("u_vt_settings_2", bgfx::UniformType::Vec4); + s_vt_page_table = bgfx::createUniform("s_vt_page_table", bgfx::UniformType::Sampler); + s_vt_texture_atlas = bgfx::createUniform("s_vt_texture_atlas", bgfx::UniformType::Sampler); +} + +VirtualTexture::~VirtualTexture() +{ + // Destroy + BX_DELETE(VirtualTexture::getAllocator(), m_indexer); + BX_DELETE(VirtualTexture::getAllocator(), m_atlas); + BX_DELETE(VirtualTexture::getAllocator(), m_loader); + BX_DELETE(VirtualTexture::getAllocator(), m_cache); + BX_DELETE(VirtualTexture::getAllocator(), m_pageTable); + // Destroy all uniforms and textures + bgfx::destroy(u_vt_settings_1); + bgfx::destroy(u_vt_settings_2); + bgfx::destroy(s_vt_page_table); + bgfx::destroy(s_vt_texture_atlas); +} + +int VirtualTexture::getMipBias() const +{ + return m_mipBias; +} + +void VirtualTexture::setMipBias(int value) +{ + m_mipBias = bx::max(0, value); +} + +void VirtualTexture::setUniforms() +{ + // Set uniforms + struct + { + struct + { + float VirtualTextureSize; + float ooAtlasScale; + float BorderScale; + float BorderOffset; + } m_settings_1; + struct + { + float MipBias; + float PageTableSize; + float unused1; + float unused2; + } m_settings_2; + } uniforms; + // Fill uniforms + int pagesize = m_info->GetPageSize(); + uniforms.m_settings_1.VirtualTextureSize = (float)m_info->m_virtualTextureSize; + uniforms.m_settings_1.ooAtlasScale = 1.0f / (float)m_atlasCount; + uniforms.m_settings_1.BorderScale = (float)((pagesize - 2.0f * m_info->m_borderSize) / pagesize); + uniforms.m_settings_1.BorderOffset = (float)m_info->m_borderSize / (float)pagesize; + uniforms.m_settings_2.MipBias = (float)m_mipBias; + uniforms.m_settings_2.PageTableSize = (float)m_info->GetPageTableSize(); + uniforms.m_settings_2.unused1 = uniforms.m_settings_2.unused2 = 0.0f; + // Set uniforms + bgfx::setUniform(u_vt_settings_1, &uniforms.m_settings_1); + bgfx::setUniform(u_vt_settings_2, &uniforms.m_settings_2); + // Set textures + bgfx::setTexture(0, s_vt_page_table, m_pageTable->getTexture()); + bgfx::setTexture(1, s_vt_texture_atlas, m_atlas->getTexture()); +} + +void VirtualTexture::setUploadsPerFrame(int count) +{ + m_uploadsPerFrame = count; + m_atlas->setUploadsPerFrame(count); +} + +int VirtualTexture::getUploadsPerFrame() const +{ + return m_uploadsPerFrame; +} + + +void VirtualTexture::enableShowBoarders(bool enable) +{ + if (m_loader->m_showBorders == enable) + { + return; + } + m_loader->m_showBorders = enable; + clear(); +} + +bool VirtualTexture::isShowBoardersEnabled() const +{ + return m_loader->m_showBorders; +} + +void VirtualTexture::enableColorMipLevels(bool enable) +{ + if (m_loader->m_colorMipLevels == enable) + { + return; + } + m_loader->m_colorMipLevels = enable; + clear(); +} + +bool VirtualTexture::isColorMipLevelsEnabled() const +{ + return m_loader->m_colorMipLevels; +} + +bgfx::TextureHandle VirtualTexture::getAtlastTexture() +{ + return m_atlas->getTexture(); +} + +bgfx::TextureHandle VirtualTexture::getPageTableTexture() +{ + return m_pageTable->getTexture(); +} + +void VirtualTexture::clear() +{ + m_cache->clear(); +} + +void VirtualTexture::update(const tinystl::vector<int>& requests, bgfx::ViewId blitViewId) +{ + m_pagesToLoad.clear(); + + // Find out what is already in memory + // If it is, update it's position in the LRU collection + // Otherwise add it to the list of pages to load + int touched = 0; + for (int i = 0; i < (int)requests.size(); ++i) + { + if (requests[i] > 0) + { + PageCount pc(m_indexer->getPageFromIndex(i), requests[i]); + if (!m_cache->touch(pc.m_page)) + { + m_pagesToLoad.push_back(pc); + } + else + { + ++touched; + } + } + } + + // Check to make sure we don't thrash + if (touched < m_atlasCount * m_atlasCount) + { + // sort by low res to high res and number of requests + bx::quickSort( + m_pagesToLoad.begin() + , uint32_t(m_pagesToLoad.size()) + , sizeof(vt::PageCount) + , [](const void* _a, const void* _b) -> int32_t { + const vt::PageCount& lhs = *(const vt::PageCount*)(_a); + const vt::PageCount& rhs = *(const vt::PageCount*)(_b); + return lhs.compareTo(rhs); + }); + + // if more pages than will fit in memory or more than update per frame drop high res pages with lowest use count + int loadcount = bx::min(bx::min((int)m_pagesToLoad.size(), m_uploadsPerFrame), m_atlasCount * m_atlasCount); + for (int i = 0; i < loadcount; ++i) + m_cache->request(m_pagesToLoad[i].m_page, blitViewId); + } + else + { + // The problem here is that all pages in cache are requested and the new or high res ones don't get uploaded + // We can adjust the mip bias to make it all fit. This solves the problem of page cache thrashing + --m_mipBias; + } + + // Update the page table + m_pageTable->update(blitViewId); +} + +bx::AllocatorI* VirtualTexture::s_allocator = nullptr; + +void VirtualTexture::setAllocator(bx::AllocatorI* allocator) +{ + s_allocator = allocator; +} + +bx::AllocatorI* VirtualTexture::getAllocator() +{ + return s_allocator; +} + +TileDataFile::TileDataFile(const bx::FilePath& filename, VirtualTextureInfo* _info, bool _readWrite) : m_info(_info) +{ + const char* access = _readWrite ? "w+b" : "rb"; + m_file = fopen(filename.getCPtr(), access); + m_size = m_info->GetPageSize() * m_info->GetPageSize() * s_channelCount; +} + +TileDataFile::~TileDataFile() +{ + fclose(m_file); +} + +void TileDataFile::readInfo() +{ + fseek(m_file, 0, SEEK_SET); + auto ret = fread(m_info, sizeof(*m_info), 1, m_file); + BX_UNUSED(ret); + m_size = m_info->GetPageSize() * m_info->GetPageSize() * s_channelCount; +} + +void TileDataFile::writeInfo() +{ + fseek(m_file, 0, SEEK_SET); + auto ret = fwrite(m_info, sizeof(*m_info), 1, m_file); + BX_UNUSED(ret); +} + +void TileDataFile::readPage(int index, uint8_t* data) +{ + fseek(m_file, m_size * index + s_tileFileDataOffset, SEEK_SET); + auto ret = fread(data, m_size, 1, m_file); + BX_UNUSED(ret); +} + +void TileDataFile::writePage(int index, uint8_t* data) +{ + fseek(m_file, m_size * index + s_tileFileDataOffset, SEEK_SET); + auto ret = fwrite(data, m_size, 1, m_file); + BX_UNUSED(ret); +} + +// TileGenerator +TileGenerator::TileGenerator(VirtualTextureInfo* _info) + : m_info(_info) + , m_indexer(nullptr) + , m_tileDataFile(nullptr) + , m_sourceImage(nullptr) + , m_page1Image(nullptr) + , m_page2Image(nullptr) + , m_2xtileImage(nullptr) + , m_4xtileImage(nullptr) + , m_tileImage(nullptr) +{ + m_tilesize = m_info->m_tileSize; + m_pagesize = m_info->GetPageSize(); +} + +TileGenerator::~TileGenerator() +{ + if (m_sourceImage != nullptr) + { + bimg::imageFree(m_sourceImage); + } + + BX_DELETE(VirtualTexture::getAllocator(), m_indexer); + + BX_DELETE(VirtualTexture::getAllocator(), m_page1Image); + BX_DELETE(VirtualTexture::getAllocator(), m_page2Image); + BX_DELETE(VirtualTexture::getAllocator(), m_2xtileImage); + BX_DELETE(VirtualTexture::getAllocator(), m_4xtileImage); + BX_DELETE(VirtualTexture::getAllocator(), m_tileImage); +} + +bool TileGenerator::generate(const bx::FilePath& _filePath) +{ + const bx::StringView baseName = _filePath.getBaseName(); + + // Generate cache filename + char tmp[256]; + bx::snprintf(tmp, sizeof(tmp), "%.*s.vt", baseName.getLength(), baseName.getPtr() ); + + bx::FilePath cacheFilePath("temp"); + cacheFilePath.join(tmp); + + // Check if tile file already exist + { + bx::Error err; + bx::FileReader fileReader; + + if (bx::open(&fileReader, cacheFilePath, &err) ) + { + bx::close(&fileReader); + + bx::debugPrintf("Tile data file '%s' already exists. Skipping generation.\n", cacheFilePath.getCPtr() ); + return true; + } + } + + // Read image + { + bx::debugPrintf("Reading image '%s'.\n", _filePath.getCPtr() ); + + bx::Error err; + bx::FileReader fileReader; + + if (!bx::open(&fileReader, _filePath, &err) ) + { + bx::debugPrintf("Image open failed'%s'.\n", _filePath.getCPtr() ); + return false; + } + + int64_t size = bx::getSize(&fileReader); + + if (0 == size) + { + bx::debugPrintf("Image '%s' size is 0.\n", _filePath.getCPtr() ); + return false; + } + + uint8_t* rawImage = (uint8_t*)BX_ALLOC(VirtualTexture::getAllocator(), size_t(size) ); + + bx::read(&fileReader, rawImage, int32_t(size), &err); + bx::close(&fileReader); + + if (!err.isOk() ) + { + bx::debugPrintf("Image read failed'%s'.\n", _filePath.getCPtr() ); + BX_FREE(VirtualTexture::getAllocator(), rawImage); + return false; + } + + m_sourceImage = bimg::imageParse(VirtualTexture::getAllocator(), rawImage, uint32_t(size), bimg::TextureFormat::BGRA8, &err); + BX_FREE(VirtualTexture::getAllocator(), rawImage); + + if (!err.isOk() ) + { + bx::debugPrintf("Image parse failed'%s'.\n", _filePath.getCPtr() ); + return false; + } + } + + // Setup + m_info->m_virtualTextureSize = int(m_sourceImage->m_width); + m_indexer = BX_NEW(VirtualTexture::getAllocator(), PageIndexer)(m_info); + + // Open tile data file + m_tileDataFile = BX_NEW(VirtualTexture::getAllocator(), TileDataFile)(cacheFilePath, m_info, true); + m_page1Image = BX_NEW(VirtualTexture::getAllocator(), SimpleImage)(m_pagesize, m_pagesize, s_channelCount, 0xff); + m_page2Image = BX_NEW(VirtualTexture::getAllocator(), SimpleImage)(m_pagesize, m_pagesize, s_channelCount, 0xff); + m_tileImage = BX_NEW(VirtualTexture::getAllocator(), SimpleImage)(m_tilesize, m_tilesize, s_channelCount, 0xff); + m_2xtileImage = BX_NEW(VirtualTexture::getAllocator(), SimpleImage)(m_tilesize * 2, m_tilesize * 2, s_channelCount, 0xff); + m_4xtileImage = BX_NEW(VirtualTexture::getAllocator(), SimpleImage)(m_tilesize * 4, m_tilesize * 4, s_channelCount, 0xff); + + // Generate tiles + bx::debugPrintf("Generating tiles\n"); + auto mipcount = m_indexer->getMipCount(); + for (int i = 0; i < mipcount; ++i) + { + int count = (m_info->m_virtualTextureSize / m_tilesize) >> i; + bx::debugPrintf("Generating Mip:%d Count:%dx%d\n", i, count, count); + for (int y = 0; y < count; ++y) + { + for (int x = 0; x < count; ++x) + { + Page page = { x, y, i }; + int index = m_indexer->getIndexFromPage(page); + CopyTile(*m_page1Image, page); + m_tileDataFile->writePage(index, &m_page1Image->m_data[0]); + } + } + } + bx::debugPrintf("Finising\n"); + // Write header + m_tileDataFile->writeInfo(); + // Close tile file + BX_DELETE(VirtualTexture::getAllocator(), m_tileDataFile); + m_tileDataFile = nullptr; + bx::debugPrintf("Done!\n"); + return true; +} + +void TileGenerator::CopyTile(SimpleImage& image, Page request) +{ + if (request.m_mip == 0) + { + int x = request.m_x * m_tilesize - m_info->m_borderSize; + int y = request.m_y * m_tilesize - m_info->m_borderSize; + // Copy sub-image with border + auto srcPitch = m_sourceImage->m_width * s_channelCount; + auto src = (uint8_t*)m_sourceImage->m_data; + auto dstPitch = image.m_width * image.m_channelCount; + auto dst = &image.m_data[0]; + for (int iy = 0; iy < m_pagesize; ++iy) + { + int ry = bx::clamp(y + iy, 0, (int)m_sourceImage->m_height - 1); + for (int ix = 0; ix < m_pagesize; ++ix) + { + int rx = bx::clamp(x + ix, 0, (int)m_sourceImage->m_width - 1); + bx::memCopy(&dst[iy * dstPitch + ix * image.m_channelCount], &src[ry * srcPitch + rx * s_channelCount], image.m_channelCount); + } + } + } + else + { + int xpos = request.m_x << 1; + int ypos = request.m_y << 1; + int mip = request.m_mip - 1; + + int size = m_info->GetPageTableSize() >> mip; + + m_4xtileImage->clear((uint8_t)request.m_mip); + + for (int y = 0; y < 4; ++y) + { + for (int x = 0; x < 4; ++x) + { + Page page = { xpos + x - 1, ypos + y - 1, mip }; + + // Wrap so we get the border sections of other pages + page.m_x = (int)bx::mod((float)page.m_x, (float)size); + page.m_y = (int)bx::mod((float)page.m_y, (float)size); + + m_tileDataFile->readPage(m_indexer->getIndexFromPage(page), &m_page2Image->m_data[0]); + + Rect src_rect = { m_info->m_borderSize, m_info->m_borderSize, m_tilesize, m_tilesize }; + Point dst_offset = { x * m_tilesize, y * m_tilesize }; + + m_4xtileImage->copy(dst_offset, *m_page2Image, src_rect); + } + } + + SimpleImage::mipmap(&m_4xtileImage->m_data[0], m_4xtileImage->m_width, s_channelCount, &m_2xtileImage->m_data[0]); + + Rect srect = { m_tilesize / 2 - m_info->m_borderSize, m_tilesize / 2 - m_info->m_borderSize, m_pagesize, m_pagesize }; + image.copy({ 0,0 }, *m_2xtileImage, srect); + } +} + +} // namespace vt |