2
0
Эх сурвалжийг харах

* 40-svt: Added to project. Added screenshot. Built shaders.

Aleš Mlakar 7 жил өмнө
parent
commit
1c6c7378e8

+ 18 - 0
examples/40-svt/fs_vt_mip.sc

@@ -0,0 +1,18 @@
+$input v_texcoord0
+
+/*
+ * Copyright 2011-2018 Branimir Karadzic. All rights reserved.
+ * License: https://github.com/bkaradzic/bgfx#license-bsd-2-clause
+ */
+
+#include "../common/common.sh"
+#include "virtualtexture.sh"
+
+void main()
+{
+   float mipCount = log2(PageTableSize);
+	float mip = floor(MipLevel(v_texcoord0.xy, VirtualTextureSize) - MipBias);
+	mip = clamp(mip, 0, mipCount);
+	vec2 offset = floor(v_texcoord0.xy * PageTableSize);
+   gl_FragColor = vec4(floor(vec3(offset / exp2(mip), mip)) / 255.0, 1.0);   
+}

+ 15 - 0
examples/40-svt/fs_vt_unlit.sc

@@ -0,0 +1,15 @@
+$input v_texcoord0
+
+/*
+ * Copyright 2011-2018 Branimir Karadzic. All rights reserved.
+ * License: https://github.com/bkaradzic/bgfx#license-bsd-2-clause
+ */
+ 
+#include "../common/common.sh"
+#include "virtualtexture.sh"
+
+void main()
+{
+   gl_FragColor = VirtualTexture(v_texcoord0.xy);
+}
+

+ 10 - 0
examples/40-svt/makefile

@@ -0,0 +1,10 @@
+#
+# Copyright 2011-2018 Branimir Karadzic. All rights reserved.
+# License: https://github.com/bkaradzic/bgfx#license-bsd-2-clause
+#
+
+BGFX_DIR=../..
+RUNTIME_DIR=$(BGFX_DIR)/examples/runtime
+BUILD_DIR=../../.build
+
+include $(BGFX_DIR)/scripts/shader.mk

BIN
examples/40-svt/screenshot.png


+ 371 - 0
examples/40-svt/svt.cpp

@@ -0,0 +1,371 @@
+/*
+ * Copyright 2018 Aleš Mlakar. All rights reserved.
+ * License: https://github.com/bkaradzic/bgfx#license-bsd-2-clause
+ */
+
+ /*
+  * Reference(s):
+  * - Sparse Virtual Textures by Sean Barrett
+  *   http://silverspaceship.com/src/svt/
+  * - Virtual Texture Demo by Brad Blanchard
+  *	  http://linedef.com/virtual-texture-demo.html
+  * - Mars texture
+  *   http://www.celestiamotherlode.net/catalog/mars.php
+  */
+
+#include "common.h"
+#include "bgfx_utils.h"
+#include "imgui/imgui.h"
+#include "camera.h"
+#include "bounds.h"
+#include "vt.h"
+
+namespace
+{
+
+struct PosTexcoordVertex
+{
+	float    m_x;
+	float    m_y;
+	float    m_z;
+	float    m_u;
+	float    m_v;
+
+	static void init()
+	{
+		ms_decl
+			.begin()
+			.add(bgfx::Attrib::Position, 3, bgfx::AttribType::Float)
+			.add(bgfx::Attrib::TexCoord0, 2, bgfx::AttribType::Float)
+			.end();
+	};
+
+	static bgfx::VertexDecl ms_decl;
+};
+
+bgfx::VertexDecl PosTexcoordVertex::ms_decl;
+
+static const float s_planeScale = 50.0f;
+
+static PosTexcoordVertex s_vplaneVertices[] =
+{
+	{ -s_planeScale, 0.0f,  s_planeScale, 1.0f, 1.0f },
+	{  s_planeScale, 0.0f,  s_planeScale, 1.0f, 0.0f },
+	{ -s_planeScale, 0.0f, -s_planeScale, 0.0f, 1.0f },
+	{  s_planeScale, 0.0f, -s_planeScale, 0.0f, 0.0f },
+};
+
+static const uint16_t s_planeIndices[] =
+{
+	0, 1, 2,
+	1, 3, 2,
+};
+
+class ExampleSVT : public entry::AppI
+{
+public:
+	ExampleSVT(const char* _name, const char* _description)
+		: entry::AppI(_name, _description)
+	{
+	}
+
+	void init(int32_t _argc, const char* const* _argv, uint32_t _width, uint32_t _height) override
+	{
+		Args args(_argc, _argv);
+
+		m_width = _width;
+		m_height = _height;
+		m_debug = BGFX_DEBUG_TEXT;
+		m_reset = BGFX_RESET_VSYNC;
+
+		bgfx::Init init;
+
+		init.type = args.m_type;
+		init.vendorId = args.m_pciId;
+		init.resolution.width = m_width;
+		init.resolution.height = m_height;
+		init.resolution.reset = m_reset;
+		bgfx::init(init);
+
+		// Enable m_debug text.
+		bgfx::setDebug(m_debug);
+
+		// Set views clear state (first pass to 0, second pass to some background color)
+		for (uint16_t i = 0; i < 2; ++i)
+		{
+			bgfx::setViewClear(i
+				, BGFX_CLEAR_COLOR | BGFX_CLEAR_DEPTH
+				, i == 0 ? 0 : 0x101050ff
+				, 1.0f
+				, 0
+			);
+		}
+
+		// Create vertex stream declaration.
+		PosTexcoordVertex::init();
+
+		// Create static vertex buffer.
+		m_vbh = bgfx::createVertexBuffer(
+			  bgfx::makeRef(s_vplaneVertices, sizeof(s_vplaneVertices))
+			, PosTexcoordVertex::ms_decl
+		);
+
+		m_ibh = bgfx::createIndexBuffer(
+			  bgfx::makeRef(s_planeIndices, sizeof(s_planeIndices))
+		);
+
+		// Create program from shaders.
+		m_vt_unlit = loadProgram("vs_vt_generic", "fs_vt_unlit");
+		m_vt_mip = loadProgram("vs_vt_generic", "fs_vt_mip");
+
+		// Imgui.
+		imguiCreate();
+
+		m_timeOffset = bx::getHPCounter();
+
+		// Get renderer capabilities info.
+		m_caps = bgfx::getCaps();
+
+		m_scrollArea = 0;
+
+		// Create and setup camera
+		cameraCreate();
+
+		cameraSetPosition({ 0.0f, 5.0f, 0.0f });
+		cameraSetVerticalAngle(0.0f);
+
+		// Create Virtual texture info
+		m_vti = new vt::VirtualTextureInfo();
+		m_vti->m_virtualTextureSize = 8192; // The actual size will be read from the tile data file
+		m_vti->m_tileSize = 128;
+		m_vti->m_borderSize = 1;
+
+		// Generate tile data file (if not yet created)
+		{
+			vt::TileGenerator tileGenerator(m_vti);
+			tileGenerator.generate("textures/8k_mars.jpg");
+		}
+
+		// Load tile data file
+		auto tileDataFile = new vt::TileDataFile("textures/8k_mars.jpg.cache", m_vti);
+		tileDataFile->readInfo();
+
+		// Create virtual texture and feedback buffer
+		m_vt = new vt::VirtualTexture(tileDataFile, m_vti, 2048, 1);
+		m_feedbackBuffer = new vt::FeedbackBuffer(m_vti, 64, 64);
+
+	}
+
+	virtual int shutdown() override
+	{
+		// Cleanup.
+		bgfx::frame();
+
+		cameraDestroy();
+		imguiDestroy();
+
+		bgfx::destroy(m_ibh);
+		bgfx::destroy(m_vbh);
+
+		bgfx::destroy(m_vt_unlit);
+		bgfx::destroy(m_vt_mip);
+
+		delete m_vti;
+		delete m_vt;
+		delete m_feedbackBuffer;
+
+		// Shutdown bgfx.
+		bgfx::shutdown();
+
+		return 0;
+	}
+
+	bool update() override
+	{
+		if (!entry::processEvents(m_width, m_height, m_debug, m_reset, &m_mouseState))
+		{
+			imguiBeginFrame(
+				  m_mouseState.m_mx
+				, m_mouseState.m_my
+				, (m_mouseState.m_buttons[entry::MouseButton::Left] ? IMGUI_MBUT_LEFT : 0)
+				| (m_mouseState.m_buttons[entry::MouseButton::Right] ? IMGUI_MBUT_RIGHT : 0)
+				| (m_mouseState.m_buttons[entry::MouseButton::Middle] ? IMGUI_MBUT_MIDDLE : 0)
+				, m_mouseState.m_mz
+				, uint16_t(m_width)
+				, uint16_t(m_height)
+			);
+
+			showExampleDialog(this);
+
+			int64_t now = bx::getHPCounter();
+			static int64_t last = now;
+			const int64_t frameTime = now - last;
+			last = now;
+			const double freq = double(bx::getHPFrequency());
+			const float deltaTime = float(frameTime / freq);
+
+			float time = (float)((now - m_timeOffset) / freq);
+
+			if ((BGFX_CAPS_TEXTURE_BLIT | BGFX_CAPS_TEXTURE_READ_BACK) != (bgfx::getCaps()->supported & (BGFX_CAPS_TEXTURE_BLIT | BGFX_CAPS_TEXTURE_READ_BACK)))
+			{
+				// When texture read-back or blit is not supported by GPU blink!				
+				bool blink = uint32_t(time*3.0f) & 1;
+				bgfx::dbgTextPrintf(0, 0, blink ? 0x4f : 0x04, " Texture read-back and/or blit not supported by GPU. ");
+
+				// Set view 0 default viewport.
+				bgfx::setViewRect(0, 0, 0, uint16_t(m_width), uint16_t(m_height));
+
+				// 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);
+			}
+			else
+			{
+				ImGui::SetNextWindowPos(
+					  ImVec2(m_width - m_width / 5.0f - 10.0f, 10.0f)
+					, ImGuiCond_FirstUseEver
+				);
+				ImGui::SetNextWindowSize(
+					  ImVec2(m_width / 5.0f, m_height - 10.0f)
+					, ImGuiCond_FirstUseEver
+				);
+				ImGui::Begin("Settings"
+					, NULL
+					, 0
+				);
+
+				//ImGui::SliderFloat("intensity", &m_intensity, 0.0f, 3.0f);
+				auto showBorders = m_vt->isShowBoardersEnabled();
+				if (ImGui::Checkbox("Show borders", &showBorders))
+				{
+					m_vt->enableShowBoarders(showBorders);
+				}
+				auto colorMipLevels = m_vt->isColorMipLevelsEnabled();
+				if (ImGui::Checkbox("Color mip levels", &colorMipLevels))
+				{
+					m_vt->enableColorMipLevels(colorMipLevels);
+				}
+				auto uploadsperframe = m_vt->getUploadsPerFrame();
+				if (ImGui::InputInt("Updates per frame", &uploadsperframe, 1, 2))
+				{
+					uploadsperframe = bx::clamp(uploadsperframe, 1, 100);
+					m_vt->setUploadsPerFrame(uploadsperframe);
+				}
+
+				ImGui::ImageButton(m_vt->getAtlastTexture(), ImVec2(m_width / 5.0f - 16.0f, m_width / 5.0f - 16.0f));
+				ImGui::ImageButton(bgfx::getTexture(m_feedbackBuffer->getFrameBuffer()), ImVec2(m_width / 5.0f - 16.0f, m_width / 5.0f - 16.0f));
+
+				ImGui::End();
+
+				// Update camera.
+				cameraUpdate(deltaTime, m_mouseState);
+
+				float view[16];
+				cameraGetViewMtx(view);
+
+				float proj[16];
+				bx::mtxProj(proj, 60.0f, float(m_width) / float(m_height), 0.1f, 1000.0f, m_caps->homogeneousDepth);
+
+				// Setup views
+				for (uint16_t i = 0; i < 2; ++i)
+				{
+					uint16_t viewWidth = 0;
+					uint16_t viewHeight = 0;
+					// Setup pass, first pass is into mip-map feedback buffer, second pass is on screen
+					if (i == 0)
+					{
+						bgfx::setViewFrameBuffer(i, m_feedbackBuffer->getFrameBuffer());
+						viewWidth = uint16_t(m_feedbackBuffer->getWidth());
+						viewHeight = uint16_t(m_feedbackBuffer->getHeight());
+					}
+					else
+					{
+						bgfx::FrameBufferHandle invalid = BGFX_INVALID_HANDLE;
+						bgfx::setViewFrameBuffer(i, invalid);
+						viewWidth = uint16_t(m_width);
+						viewHeight = uint16_t(m_height);
+					}
+
+					bgfx::setViewRect(i, 0, 0, viewWidth, viewHeight);
+					bgfx::setViewTransform(i, view, proj);
+
+					float mtx[16];
+					bx::mtxIdentity(mtx);
+
+					// Set identity transform for draw call.
+					bgfx::setTransform(mtx);
+
+					// Set vertex and index buffer.
+					bgfx::setVertexBuffer(0, m_vbh);
+					bgfx::setIndexBuffer(m_ibh);
+
+					// Set render states.
+					bgfx::setState(0
+						| BGFX_STATE_WRITE_RGB
+						| BGFX_STATE_WRITE_A
+						| BGFX_STATE_WRITE_Z
+						| BGFX_STATE_DEPTH_TEST_LESS
+					);
+
+					// Set virtual texture uniforms
+					m_vt->setUniforms();
+
+					// Submit primitive for rendering to first pass (to feedback buffer, where mip levels and tile x/y will be rendered
+					if (i == 0)
+					{
+						bgfx::submit(i, m_vt_mip);
+						// Download previous frame feedback info
+						m_feedbackBuffer->download();
+						// Update and upload new requests
+						m_vt->update(m_feedbackBuffer->getRequests(), 4);
+						// Clear feedback
+						m_feedbackBuffer->clear();
+						// Copy new frame feedback buffer
+						m_feedbackBuffer->copy(3);
+					}
+					else
+					{
+						// Submit primitive for rendering to second pass (to back buffer, where virtual texture page table and atlas will be used)
+						bgfx::submit(i, m_vt_unlit);
+					}
+				}
+			}
+
+			imguiEndFrame();
+
+			// Advance to next frame. Rendering thread will be kicked to
+			// process submitted rendering primitives.
+			bgfx::frame();
+
+			return true;
+		}
+
+		return false;
+	}
+
+	bgfx::VertexBufferHandle m_vbh;
+	bgfx::IndexBufferHandle m_ibh;
+
+	bgfx::ProgramHandle m_vt_unlit;
+	bgfx::ProgramHandle m_vt_mip;
+
+	uint32_t m_width;
+	uint32_t m_height;
+	uint32_t m_debug;
+	uint32_t m_reset;
+
+	int32_t m_scrollArea;
+
+	entry::MouseState m_mouseState;
+
+	const bgfx::Caps* m_caps;
+	int64_t m_timeOffset;
+
+	vt::VirtualTextureInfo* m_vti;
+	vt::VirtualTexture* m_vt;
+	vt::FeedbackBuffer* m_feedbackBuffer;
+};
+
+} // namespace
+
+ENTRY_IMPLEMENT_MAIN(ExampleSVT, "40-svt", "Sparse Virtual Textures.");

+ 4 - 0
examples/40-svt/varying.def.sc

@@ -0,0 +1,4 @@
+vec2 v_texcoord0 : TEXCOORD0 = vec2(0.0, 0.0);
+
+vec3 a_position  : POSITION;
+vec2 a_texcoord0 : TEXCOORD0;

+ 80 - 0
examples/40-svt/virtualtexture.sh

@@ -0,0 +1,80 @@
+/*
+ * Copyright 2011-2018 Branimir Karadzic. All rights reserved.
+ * License: https://github.com/bkaradzic/bgfx#license-bsd-2-clause
+ */
+ 
+uniform vec4 u_vt_settings_1;
+uniform vec4 u_vt_settings_2;
+
+#define VirtualTextureSize u_vt_settings_1.x
+#define AtlasScale u_vt_settings_1.y
+#define BorderScale u_vt_settings_1.z
+#define BorderOffset u_vt_settings_1.w
+
+#define MipBias u_vt_settings_2.x
+#define PageTableSize u_vt_settings_2.y
+
+SAMPLER2D(s_vt_page_table,  0);
+SAMPLER2D(s_vt_texture_atlas,  1);
+
+// This function estimates mipmap levels
+float MipLevel( vec2 uv, float size )
+{
+	vec2 dx = dFdx( uv * size );
+    vec2 dy = dFdy( uv * size );
+    float d = max( dot( dx, dx ), dot( dy, dy ) );
+
+	return max( 0.5 * log2( d ), 0 );
+}
+
+// This function samples the page table and returns the page's 
+// position and mip level. 
+vec3 SampleTable( vec2 uv, float mip )
+{
+	vec2 offset = fract( uv * PageTableSize ) / PageTableSize;
+	return texture2DLod( s_vt_page_table, uv - offset, mip ).xyz;
+}
+
+// This functions samples from the texture atlas and returns the final color
+vec4 SampleAtlas( vec3 page, vec2 uv )
+{
+	float mipsize = exp2( floor( page.z * 255.0 + 0.5 ) );
+
+	uv = fract( uv * PageTableSize / mipsize );
+
+	uv *= BorderScale;
+	uv += BorderOffset;
+
+	vec2 offset = floor( page.xy * 255 + 0.5 );
+
+	return texture2D( s_vt_texture_atlas, ( offset + uv ) * AtlasScale );
+}
+
+// Ugly brute force trilinear, look up twice and mix
+vec4 VirtualTextureTrilinear( vec2 uv )
+{
+	float miplevel = MipLevel( uv, VirtualTextureSize );
+	miplevel = clamp( miplevel, 0, log2( PageTableSize )-1 );
+
+	float mip1     = floor( miplevel );
+	float mip2	 = mip1 + 1;
+	float mipfrac  = miplevel - mip1;
+
+	vec3 page1 = SampleTable( uv, mip1 );
+	vec3 page2 = SampleTable( uv, mip2 );
+
+	vec4 sample1 = SampleAtlas( page1, uv );
+	vec4 sample2 = SampleAtlas( page2, uv );
+
+	return mix( sample1, sample2, mipfrac );
+}
+
+// Simple bilinear
+vec4 VirtualTexture( vec2 uv )
+{
+	float mip = floor( MipLevel( uv, VirtualTextureSize ) );
+	mip = clamp( mip, 0, log2( PageTableSize ) );
+
+	vec3 page = SampleTable( uv, mip );
+	return SampleAtlas( page, uv );
+}

+ 16 - 0
examples/40-svt/vs_vt_generic.sc

@@ -0,0 +1,16 @@
+$input a_position, a_texcoord0
+$output v_texcoord0
+
+/*
+ * Copyright 2011-2018 Branimir Karadzic. All rights reserved.
+ * License: https://github.com/bkaradzic/bgfx#license-bsd-2-clause
+ */
+
+#include "../common/common.sh"
+
+void main()
+{
+	vec3 wpos = mul(u_model[0], vec4(a_position, 1.0) ).xyz;
+	gl_Position = mul(u_viewProj, vec4(wpos, 1.0) );
+	v_texcoord0 = a_texcoord0;
+}

+ 1248 - 0
examples/40-svt/vt.cpp

@@ -0,0 +1,1248 @@
+#include "vt.h"
+#include <bx/file.h>
+#include <algorithm>
+
+namespace vt
+{
+
+// Constants
+static const int ChannelCount = 4;
+static const int StagingTextureCount = 1;
+static const int TileFileDataOffset = sizeof(VirtualTextureInfo);
+
+// Page
+uint64_t Page::hash() const
+{
+	return (uint64_t(m_mip) << 32) | uint64_t((uint32_t(m_x) << 16) | uint32_t(m_y));
+}
+
+bool Page::operator==(const Page& page) const
+{
+	return hash() == page.hash();
+}
+
+bool Page::operator<(const Page& page) const
+{
+	return hash() < page.hash();
+}
+
+// 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
+}
+
+bool PageCount::operator==(const PageCount& other) const
+{
+	return compareTo(other) == 0;
+}
+
+bool PageCount::operator<(const PageCount& other) const
+{
+	return compareTo(other) < 0;
+}
+
+// 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 < m_stagingTextures.size(); ++i)
+	{
+		bgfx::destroy(m_stagingTextures[i]);
+	}
+}
+
+void StagingPool::grow(int count)
+{
+	while (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) % m_stagingTextures.size();
+}
+
+// PageIndexer
+PageIndexer::PageIndexer(VirtualTextureInfo* _info)
+	: m_info(_info)
+{
+	m_mipcount = (int)(log2(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;
+}
+
+// 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, std::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)
+		{
+			delete 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] = new 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)
+	{
+		delete 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
+{
+	int size = m_info->GetPageTableSize();
+	m_quadtree = new Quadtree({ 0, 0, size, size }, (int)log2(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); pt; };
+
+	int PageTableSizeLog2 = (int)log2(m_info->GetPageTableSize());
+
+	for (int i = 0; i < PageTableSizeLog2 + 1; ++i)
+	{
+		int  mipSize = m_info->GetPageTableSize() >> i;
+		auto simpleImage = new SimpleImage(mipSize, mipSize, 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()
+{
+	delete m_quadtree;
+	bgfx::destroy(m_texture);
+	for (int i = 0; i < m_images.size(); ++i)
+	{
+		delete m_images[i];
+	}
+	for (int i = 0; i < m_stagingTextures.size(); ++i)
+	{
+		bgfx::destroy(m_stagingTextures[i]);
+	}
+}
+
+void PageTable::update(bgfx::ViewId blitViewId)
+{
+	if (!m_quadtreeDirty)
+	{
+		return;
+	}
+	m_quadtreeDirty = false;
+	int PageTableSizeLog2 = (int)log2(m_info->GetPageTableSize());
+	for (int i = 0; i < PageTableSizeLog2 + 1; ++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 * 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_tileDataFile(_tileDataFile)
+	, m_indexer(_indexer)
+	, m_info(_info)
+	, m_colorMipLevels(false)
+	, m_showBorders(false)
+{
+}
+
+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() * 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 * ChannelCount + 0] = 0;
+		image[xindex * ChannelCount + 1] = 255;
+		image[xindex * ChannelCount + 2] = 0;
+		image[xindex * ChannelCount + 3] = 255;
+
+		int yindex = i * pagesize + bordersize;
+		image[yindex * ChannelCount + 0] = 0;
+		image[yindex * ChannelCount + 1] = 255;
+		image[yindex * ChannelCount + 2] = 0;
+		image[yindex * 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) * ChannelCount + 0] = colors[request.m_mip].m_b;
+			image[(y * pagesize + x) * ChannelCount + 1] = colors[request.m_mip].m_g;
+			image[(y * pagesize + x) * ChannelCount + 2] = colors[request.m_mip].m_r;
+			image[(y * pagesize + x) * ChannelCount + 3] = colors[request.m_mip].m_a;
+		}
+	}
+}
+
+PageCache::PageCache(VirtualTextureInfo* _info, TextureAtlas* _atlas, PageLoader* _loader, PageIndexer* _indexer, int _count)
+	: m_info(_info)
+	, m_atlas(_atlas)
+	, m_loader(_loader)
+	, m_indexer(_indexer)
+	, 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
+			auto it = std::find(m_lru.begin(), m_lru.end(), page);
+			auto lruPage = *it;
+			m_lru.erase(it);
+			m_lru.push_back(lruPage);
+			return true;
+		}
+	}
+
+	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 * 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, StagingTextureCount, true)
+{
+	// Setup classes
+	m_indexer = new PageIndexer(m_info);
+	m_requests.resize(m_indexer->getCount());
+	// Initialize and clear buffers
+	m_downloadBuffer.resize(m_width * m_height * ChannelCount);
+	bx::memSet(&m_downloadBuffer[0], 0, m_width * m_height * 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()
+{
+	delete 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)
+{
+	int PageTableSizeLog2 = (int)log2(m_info->GetPageTableSize());
+	int count = PageTableSizeLog2 - request.m_mip + 1;
+
+	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 (!m_indexer->isValid(page))
+		{
+			return;
+		}
+
+		++m_requests[m_indexer->getIndexFromPage(page)];
+	}
+}
+
+const std::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 = new PageIndexer(m_info);
+	m_pagesToLoad.reserve(m_indexer->getCount());
+
+	// Setup classes
+	m_atlas = new TextureAtlas(m_info, m_atlasCount, m_uploadsPerFrame);
+	m_loader = new PageLoader(m_tileDataFile, m_indexer, m_info);
+	m_cache = new PageCache(m_info, m_atlas, m_loader, m_indexer, m_atlasCount);
+	m_pageTable = new 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::Int1);
+	s_vt_texture_atlas = bgfx::createUniform("s_vt_texture_atlas", bgfx::UniformType::Int1);
+}
+
+VirtualTexture::~VirtualTexture()
+{
+	// Destroy
+	delete m_indexer;
+	delete m_atlas;
+	delete m_loader;
+	delete m_cache;
+	delete 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 = value;
+	if (m_mipBias < 0)
+		m_mipBias = 0;
+}
+
+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();
+	// 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 std::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 < 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
+		std::sort(m_pagesToLoad.begin(), m_pagesToLoad.end());
+
+		// 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);
+}
+
+TileDataFile::TileDataFile(const std::string& filename, VirtualTextureInfo* _info, bool _readWrite) : m_info(_info)
+{
+	const char* access = _readWrite ? "w+b" : "rb";
+	m_file = fopen(filename.c_str(), access);
+	m_size = m_info->GetPageSize() * m_info->GetPageSize() * ChannelCount;
+}
+
+TileDataFile::~TileDataFile()
+{
+	fclose(m_file);
+}
+
+void TileDataFile::readInfo()
+{
+	fseek(m_file, 0, SEEK_SET);
+	fread(m_info, sizeof(*m_info), 1, m_file);
+	m_size = m_info->GetPageSize() * m_info->GetPageSize() * ChannelCount;
+}
+
+void TileDataFile::writeInfo()
+{
+	fseek(m_file, 0, SEEK_SET);
+	fwrite(m_info, sizeof(*m_info), 1, m_file);
+}
+
+void TileDataFile::readPage(int index, uint8_t* data)
+{
+	fseek(m_file, m_size * index + TileFileDataOffset, SEEK_SET);
+	fread(data, m_size, 1, m_file);
+}
+
+void TileDataFile::writePage(int index, uint8_t* data)
+{
+	fseek(m_file, m_size * index + TileFileDataOffset, SEEK_SET);
+	fwrite(data, m_size, 1, m_file);
+}
+
+// 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);
+	}
+
+	delete m_indexer;
+
+	delete m_page1Image;
+	delete m_page2Image;
+	delete m_2xtileImage;
+	delete m_4xtileImage;
+	delete m_tileImage;
+}
+
+bool TileGenerator::generate(const std::string& filename)
+{
+	std::string cacheFilename = filename + ".cache";
+	// Check if tile file already exist
+	{
+		bx::Error error;
+		bx::FileReader fileReader;
+		fileReader.open(bx::FilePath(cacheFilename.c_str()), &error);
+		if (error.isOk())
+		{
+			bx::debugPrintf("Tile data file '%s' already exists. Skipping generation.\n", cacheFilename.c_str());
+			return true;
+		}
+	}
+	// Read image
+	{
+		bx::debugPrintf("Reading image '%s'.\n", filename.c_str());
+		bx::Error error;
+		bx::FileReader fileReader;
+		fileReader.open(bx::FilePath(filename.c_str()), &error);
+		if (!error.isOk())
+		{
+			return false;
+		}
+		auto size = fileReader.seek(0, bx::Whence::End);
+		fileReader.seek(0, bx::Whence::Begin);
+		auto rawImage = (uint8_t*)BX_ALLOC(&m_allocator, size);
+		fileReader.read(rawImage, int32_t(size), &error);
+		fileReader.close();
+		if (!error.isOk())
+		{
+			return false;
+		}
+		m_sourceImage = bimg::imageParse(&m_allocator, rawImage, uint32_t(size), bimg::TextureFormat::BGRA8, &error);
+		BX_FREE(&m_allocator, rawImage);
+		if (!error.isOk())
+		{
+			return false;
+		}
+	}
+
+	// Setup
+	m_info->m_virtualTextureSize = int(m_sourceImage->m_width);
+	m_indexer = new PageIndexer(m_info);
+
+	// Open tile data file
+	m_tileDataFile = new TileDataFile(cacheFilename, m_info, true);
+
+	m_page1Image = new SimpleImage(m_pagesize, m_pagesize, ChannelCount, 0xff);
+	m_page2Image = new SimpleImage(m_pagesize, m_pagesize, ChannelCount, 0xff);
+	m_tileImage = new SimpleImage(m_tilesize, m_tilesize, ChannelCount, 0xff);
+	m_2xtileImage = new SimpleImage(m_tilesize * 2, m_tilesize * 2, ChannelCount, 0xff);
+	m_4xtileImage = new SimpleImage(m_tilesize * 4, m_tilesize * 4, ChannelCount, 0xff);
+
+	// Generate tiles
+	bx::debugPrintf("Generating tiles\n");
+	int mipcount = (int)log2(m_info->GetPageTableSize());
+	for (int i = 0; i < mipcount + 1; ++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
+	delete 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 * 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 * 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
+				#define Modulus(_a_, _b_) (int)(_a_ - _b_ * floorf( (float)_a_ / (float)_b_))
+				page.m_x = Modulus(page.m_x, size);
+				page.m_y = Modulus(page.m_y, size);
+				#undef Modulus
+
+				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, 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

+ 422 - 0
examples/40-svt/vt.h

@@ -0,0 +1,422 @@
+#pragma once
+#include "common.h"
+#include "bgfx_utils.h"
+#include "bimg/decode.h"
+#include <vector>
+#include <set>
+#include <functional>
+
+namespace vt
+{
+
+// Forward declarations
+class PageCache;
+class TextureAtlas;
+class TileDataFile;
+
+// Point
+struct Point
+{
+	int m_x, m_y;
+};
+
+// Rect
+struct Rect
+{
+	int minX() const
+	{
+		return m_x;
+	}
+
+	int minY() const
+	{
+		return m_y;
+	}
+
+	int maxX() const
+	{
+		return m_x + m_width;
+	}
+
+	int maxY() const
+	{
+		return m_y + m_height;
+	}
+
+	bool contains(const Point& p) const
+	{
+		return p.m_x >= minX() && p.m_y >= minY() && p.m_x < maxX() && p.m_y < maxY();
+	}
+
+	int m_x, m_y, m_width, m_height;
+};
+
+// Color
+struct Color
+{
+	uint8_t m_r, m_g, m_b, m_a;
+};
+
+// Page
+struct Page
+{
+	uint64_t hash() const;
+	bool   operator==(const Page& page) const;
+	bool   operator<(const Page& page) const;
+
+	int m_x;
+	int m_y;
+	int m_mip;
+};
+
+// PageCount
+struct PageCount
+{
+	Page m_page;
+	int  m_count;
+
+	PageCount(Page _page = Page(), int _count = 0);
+
+	int  compareTo(const PageCount& other) const;
+	bool operator==(const PageCount& other) const;
+	bool operator<(const PageCount& other) const;
+};
+
+// VirtualTextureInfo
+struct VirtualTextureInfo
+{
+	VirtualTextureInfo();
+	int GetPageSize() const;
+	int GetPageTableSize() const;
+
+	int m_virtualTextureSize = 0;
+	int m_tileSize = 0;
+	int m_borderSize = 0;
+};
+
+// StagingPool
+class StagingPool
+{
+public:
+	StagingPool(int _width, int _height, int _count, bool _readBack);
+	~StagingPool();
+
+	void grow(int count);
+
+	bgfx::TextureHandle getTexture();
+	void				next();
+
+private:
+	std::vector<bgfx::TextureHandle>  m_stagingTextures;
+
+	int			m_stagingTextureIndex;
+	int			m_width;
+	int			m_height;
+	uint64_t	m_flags;
+};
+
+// PageIndexer
+struct PageIndexer
+{
+public:
+	PageIndexer(VirtualTextureInfo* _info);
+
+	int  getIndexFromPage(Page page);
+	Page getPageFromIndex(int index);
+
+	bool isValid(Page page);
+	int  getCount() const;
+
+private:
+	VirtualTextureInfo* m_info;
+	int                 m_mipcount;
+	std::vector<int>    m_offsets; // This stores the offsets to the first page of the start of a mipmap level
+	std::vector<int>    m_sizes; // This stores the sizes of various mip levels
+	std::vector<Page>   m_reverse;
+	int					m_count;
+};
+
+// SimpleImage
+struct SimpleImage
+{
+	SimpleImage(int _width, int _height, int _channelCount, uint8_t _clearValue = 0);
+	SimpleImage(int _width, int _height, int _channelCount, std::vector<uint8_t>& _data);
+
+	void copy(Point dest_offset, SimpleImage& src, Rect src_rect);
+	void clear(uint8_t clearValue = 0);
+	void fill(Rect rect, uint8_t r, uint8_t g, uint8_t b, uint8_t a);
+
+	static void mipmap(uint8_t* source, int size, int channels, uint8_t* dest);
+
+	int					 m_width = 0;
+	int					 m_height = 0;
+	int					 m_channelCount = 0;
+	std::vector<uint8_t> m_data;
+};
+
+// Quadtree
+struct Quadtree
+{
+	Quadtree(Rect _rect, int _level);
+	~Quadtree();
+
+	void             add(Page request, Point mapping);
+	void             remove(Page request);
+	void             write(SimpleImage& image, int miplevel);
+	Rect			 getRectangle(int index);
+
+	void		     write(Quadtree* node, SimpleImage& image, int miplevel);
+	static Quadtree* findPage(Quadtree* node, Page request, int* index);
+
+	int		  m_level;
+	Rect      m_rectangle;
+	Point     m_mapping;
+	Quadtree* m_children[4];
+};
+
+// PageTable
+class PageTable
+{
+public:
+	PageTable(PageCache* _cache, VirtualTextureInfo* _info, PageIndexer* _indexer);
+	~PageTable();
+
+	void update(bgfx::ViewId blitViewId);
+	bgfx::TextureHandle getTexture();
+
+private:
+	VirtualTextureInfo* m_info;
+	bgfx::TextureHandle m_texture;
+	PageIndexer*		m_indexer;
+	Quadtree*			m_quadtree;
+	bool				m_quadtreeDirty;
+
+	std::vector<SimpleImage*>			m_images;
+	std::vector<bgfx::TextureHandle>    m_stagingTextures;
+};
+
+// PageLoader
+class PageLoader
+{
+public:
+	struct ReadState
+	{
+		Page					m_page;
+		std::vector<uint8_t>	m_data;
+	};
+
+	PageLoader(TileDataFile* _tileDataFile, PageIndexer* _indexer, VirtualTextureInfo* _info);
+	void submit(Page request);
+	void loadPage(ReadState& state);
+	void onPageLoadComplete(ReadState& state);
+	void copyBorder(uint8_t* image);
+	void copyColor(uint8_t* image, Page request);
+
+	std::function<void(Page, uint8_t*)> loadComplete;
+
+	bool m_colorMipLevels;
+	bool m_showBorders;
+
+private:
+	TileDataFile*		m_tileDataFile;
+	PageIndexer*        m_indexer;
+	VirtualTextureInfo* m_info;
+};
+
+// PageCache
+class PageCache
+{
+public:
+	PageCache(VirtualTextureInfo* _info, TextureAtlas* _atlas, PageLoader* _loader, PageIndexer* _indexer, int _count);
+	bool touch(Page page);
+	bool request(Page request, bgfx::ViewId blitViewId);
+	void clear();
+	void loadComplete(Page page, uint8_t* data);
+
+	// These callbacks are used to notify the other systems
+	std::function<void(Page, Point)> removed;
+	std::function<void(Page, Point)> added;
+
+private:
+	VirtualTextureInfo* m_info;
+	TextureAtlas*		m_atlas;
+	PageLoader*			m_loader;
+	PageIndexer*		m_indexer;
+
+	int m_count;
+
+	struct LruPage
+	{
+		Page	m_page;
+		Point	m_point;
+
+		bool operator==(const Page& other) const
+		{
+			return m_page == other;
+		}
+	};
+
+	int m_current; // This is used for generating the texture atlas indices before the lru is full
+
+	std::set<Page>       m_lru_used;
+	std::vector<LruPage> m_lru;
+	std::set<Page>       m_loading;
+
+	bgfx::ViewId m_blitViewId;
+};
+
+// TextureAtlas
+class TextureAtlas
+{
+public:
+	TextureAtlas(VirtualTextureInfo* _info, int count, int uploadsperframe);
+	~TextureAtlas();
+
+	void setUploadsPerFrame(int count);
+	void uploadPage(Point pt, uint8_t* data, bgfx::ViewId blitViewId);
+
+	bgfx::TextureHandle getTexture();
+
+private:
+	VirtualTextureInfo*  m_info;
+	bgfx::TextureHandle  m_texture;
+	StagingPool          m_stagingPool;
+};
+
+// FeedbackBuffer
+class FeedbackBuffer
+{
+public:
+	FeedbackBuffer(VirtualTextureInfo* _info, int _width, int _height);
+	~FeedbackBuffer();
+
+	void clear();
+
+	void copy(bgfx::ViewId viewId);
+	void download();
+
+	// 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 addRequestAndParents(Page request);
+
+	const std::vector<int>& getRequests() const;
+	bgfx::FrameBufferHandle getFrameBuffer();
+
+	int getWidth() const;
+	int getHeight() const;
+
+private:
+	VirtualTextureInfo* m_info;
+	PageIndexer*		m_indexer;
+
+	int m_width = 0;
+	int m_height = 0;
+
+	StagingPool				m_stagingPool;
+	bgfx::TextureHandle		m_lastStagingTexture;
+	bgfx::FrameBufferHandle m_feedbackFrameBuffer;
+
+	// This stores the pages by index.  The int value is number of requests.
+	std::vector<int>		m_requests;
+	std::vector<uint8_t>	m_downloadBuffer;
+};
+
+// VirtualTexture
+class VirtualTexture
+{
+public:
+	VirtualTexture(TileDataFile* _tileDataFile, VirtualTextureInfo* _info, int _atlassize, int _uploadsperframe, int _mipBias = 4);
+	~VirtualTexture();
+
+	int  getMipBias() const;
+	void setMipBias(int value);
+
+	void setUploadsPerFrame(int count);
+	int getUploadsPerFrame() const;
+
+	void enableShowBoarders(bool enable);
+	bool isShowBoardersEnabled() const;
+
+	void enableColorMipLevels(bool enable);
+	bool isColorMipLevelsEnabled() const;
+
+	bgfx::TextureHandle getAtlastTexture();
+	bgfx::TextureHandle getPageTableTexture();
+
+	void clear();
+	void update(const std::vector<int>& requests, bgfx::ViewId blitViewId);
+
+	void setUniforms();
+
+private:
+	TileDataFile*		m_tileDataFile;
+	VirtualTextureInfo* m_info;
+	PageIndexer*        m_indexer;
+	PageTable*          m_pageTable;
+	TextureAtlas*       m_atlas;
+	PageLoader*         m_loader;
+	PageCache*          m_cache;
+
+	int m_atlasCount;
+	int m_uploadsPerFrame;
+
+	std::vector<PageCount> m_pagesToLoad;
+
+	int m_mipBias;
+
+	bgfx::UniformHandle u_vt_settings_1;
+	bgfx::UniformHandle u_vt_settings_2;
+	bgfx::UniformHandle s_vt_page_table;
+	bgfx::UniformHandle s_vt_texture_atlas;
+};
+
+// TileDataFile
+class TileDataFile
+{
+public:
+	TileDataFile(const std::string& filename, VirtualTextureInfo* _info, bool _readWrite = false);
+	~TileDataFile();
+
+	void readInfo();
+	void writeInfo();
+
+	void readPage(int index, uint8_t* data);
+	void writePage(int index, uint8_t* data);
+
+private:
+	VirtualTextureInfo*	m_info;
+	int					m_size;
+	FILE*				m_file;
+};
+
+// TileGenerator
+class TileGenerator
+{
+public:
+	TileGenerator(VirtualTextureInfo* _info);
+	~TileGenerator();
+
+	bool generate(const std::string& filename);
+
+private:
+	void CopyTile(SimpleImage& image, Page request);
+
+private:
+	VirtualTextureInfo* m_info;
+	PageIndexer*		m_indexer;
+	TileDataFile*		m_tileDataFile;
+
+	int	m_tilesize;
+	int	m_pagesize;
+
+	bx::DefaultAllocator	m_allocator;
+	bimg::ImageContainer*	m_sourceImage;
+
+	SimpleImage* m_page1Image;
+	SimpleImage* m_page2Image;
+	SimpleImage* m_2xtileImage;
+	SimpleImage* m_4xtileImage;
+	SimpleImage* m_tileImage;
+};
+
+} // namespace vt

BIN
examples/runtime/shaders/dx11/fs_vt_mip.bin


BIN
examples/runtime/shaders/dx11/fs_vt_unlit.bin


BIN
examples/runtime/shaders/dx11/vs_vt_generic.bin


BIN
examples/runtime/shaders/glsl/fs_vt_mip.bin


BIN
examples/runtime/shaders/glsl/fs_vt_unlit.bin


BIN
examples/runtime/shaders/glsl/vs_vt_generic.bin


BIN
examples/runtime/textures/8k_mars.jpg


+ 1 - 0
scripts/genie.lua

@@ -449,6 +449,7 @@ or _OPTIONS["with-combined-examples"] then
 		, "37-gpudrivenrendering"
 		, "38-bloom"
 		, "39-assao"
+		, "40-svt"
 		)
 
 	-- C99 source doesn't compile under WinRT settings