Browse Source

- Add configurable early Z
- Improve sorting of render queue elements. It's by material and distance
- Optimize the ModelNode. Less nodes spawned now

Panagiotis Christopoulos Charitos 8 years ago
parent
commit
712c7a3813

+ 0 - 35
bled/Main.cpp

@@ -221,41 +221,6 @@ Error init(int argc, char* argv[])
 
 	// Config
 	Config config;
-	config.set("dbg.enabled", false);
-	config.set("sm.bilinearEnabled", true);
-	config.set("is.groundLightEnabled", false);
-	config.set("sm.enabled", true);
-	config.set("sm.maxLights", 16);
-	config.set("sm.poissonEnabled", false);
-	config.set("sm.resolution", 512);
-	config.set("lf.maxFlares", 32);
-	config.set("pps.enabled", true);
-	config.set("bloom.enabled", true);
-	config.set("bloom.renderingQuality", 0.5);
-	config.set("bloom.blurringDist", 1.0);
-	config.set("bloom.blurringIterationsCount", 1);
-	config.set("bloom.threshold", 2.0);
-	config.set("bloom.scale", 2.0);
-	config.set("bloom.samples", 17);
-	config.set("ssao.blurringIterationsCount", 1);
-	config.set("ssao.enabled", true);
-	config.set("ssao.renderingQuality", 0.25);
-	config.set("sslf.enabled", true);
-	config.set("pps.sharpen", false);
-	config.set("renderingQuality", 1.0);
-	config.set("width", 1920);
-	config.set("height", 1088);
-	config.set("lodDistance", 20.0);
-	config.set("samples", 1);
-	config.set("tessellation", true);
-	// config.set("maxTextureSize", 256);
-	config.set("ir.enabled", true);
-	// config.set("ir.clusterSizeZ", 32);
-	config.set("sslr.enabled", false);
-	config.set("ir.rendererSize", 64);
-	config.set("fullscreenDesktopResolution", true);
-	// config.set("clusterSizeZ", 16);
-	config.set("debugContext", false);
 	if(getenv("ANKI_DATA_PATH"))
 	{
 		config.set("dataPaths", getenv("ANKI_DATA_PATH"));

+ 2 - 2
samples/common/Framework.cpp

@@ -11,9 +11,9 @@ Error SampleApp::init(int argc, char** argv)
 {
 	// Init the super class
 	Config config;
-	config.set("fullscreenDesktopResolution", true);
+	config.set("window.fullscreenDesktopResolution", true);
 	config.set("rsrc.dataPaths", ".:../..");
-	config.set("debugContext", 0);
+	config.set("window.debugContext", 0);
 	ANKI_CHECK(config.setFromCommandLineArguments(argc, argv));
 	ANKI_CHECK(App::init(config, allocAligned, nullptr));
 

+ 1 - 0
sandbox/Main.cpp

@@ -36,6 +36,7 @@ Error MyApp::init(int argc, char* argv[])
 	Config config;
 	ANKI_CHECK(config.loadFromFile(argv[1]));
 	ANKI_CHECK(config.setFromCommandLineArguments(argc, argv));
+	// ANKI_CHECK(config.saveToFile(argv[1]));
 
 	// Init super class
 	ANKI_CHECK(App::init(config, allocAligned, nullptr));

+ 0 - 49
sandbox/config.xml

@@ -1,52 +1,3 @@
 <?xml version="1.0" encoding="UTF-8" ?>
 <config>
-	<sm.enabled>1</sm.enabled>
-	<sm.poissonEnabled>0</sm.poissonEnabled>
-	<sm.bilinearEnabled>1</sm.bilinearEnabled>
-	<sm.resolution>512</sm.resolution>
-	<sm.maxLights>16</sm.maxLights>
-	<is.maxPointLights>384</is.maxPointLights>
-	<is.maxSpotLights>16</is.maxSpotLights>
-	<is.maxSpotTexLights>8</is.maxSpotTexLights>
-	<is.maxLightsPerCluster>8</is.maxLightsPerCluster>
-	<lf.maxSpritesPerFlare>8</lf.maxSpritesPerFlare>
-	<lf.maxFlares>32</lf.maxFlares>
-	<bloom.enabled>1</bloom.enabled>
-	<bloom.renderingQuality>0.500000</bloom.renderingQuality>
-	<bloom.blurringDist>1</bloom.blurringDist>
-	<bloom.samples>17</bloom.samples>
-	<bloom.blurringIterationsCount>1</bloom.blurringIterationsCount>
-	<bloom.threshold>2.5</bloom.threshold>
-	<bloom.scale>2.5</bloom.scale>
-	<ssao.enabled>1</ssao.enabled>
-	<ssao.renderingQuality>0.250000</ssao.renderingQuality>
-	<ssao.blurringIterationsCount>1</ssao.blurringIterationsCount>
-	<sslf.enabled>1</sslf.enabled>
-	<pps.enabled>1</pps.enabled>
-	<pps.sharpen>0</pps.sharpen>
-	<pps.gammaCorrection>1</pps.gammaCorrection>
-	<ir.enabled>1</ir.enabled>
-	<ir.rendererSize>64</ir.rendererSize>
-	<ir.cubemapTextureArraySize>32</ir.cubemapTextureArraySize>
-	<sslr.enabled>0</sslr.enabled>
-	<sslr.startRoughnes>0.200000</sslr.startRoughnes>
-	<dbg.enabled>0</dbg.enabled>
-	<tm.enabled>1</tm.enabled>
-	<width>1920</width>
-	<height>1080</height>
-	<renderingQuality>1</renderingQuality>
-	<lodDistance>20</lodDistance>
-	<tessellation>1</tessellation>
-	<clusterSizeZ>32</clusterSizeZ>
-	<imageReflectionMaxDistance>30</imageReflectionMaxDistance>
-	<core.uniformPerFrameMemorySize>16777216</core.uniformPerFrameMemorySize>
-	<core.storagePerFrameMemorySize>16777216</core.storagePerFrameMemorySize>
-	<core.vertexPerFrameMemorySize>16777216</core.vertexPerFrameMemorySize>
-	<rsrc.maxTextureSize>1048576</rsrc.maxTextureSize>
-	<rsrc.textureAnisotropy>8</rsrc.textureAnisotropy>
-	<rsrc.dataPaths>assets:.</rsrc.dataPaths>
-	<glmajor>4</glmajor>
-	<glminor>5</glminor>
-	<fullscreenDesktopResolution>1</fullscreenDesktopResolution>
-	<debugContext>0</debugContext>
 </config>

+ 1 - 1
src/anki/core/App.cpp

@@ -204,7 +204,7 @@ Error App::initInternal(const ConfigSet& config_, AllocAlignedCallback allocCb,
 	nwinit.m_height = config.getNumber("height");
 	nwinit.m_depthBits = 0;
 	nwinit.m_stencilBits = 0;
-	nwinit.m_fullscreenDesktopRez = config.getNumber("fullscreenDesktopResolution");
+	nwinit.m_fullscreenDesktopRez = config.getNumber("window.fullscreenDesktopResolution");
 	m_window = m_heapAlloc.newInstance<NativeWindow>();
 
 	ANKI_CHECK(m_window->init(nwinit, m_heapAlloc));

+ 30 - 51
src/anki/core/Config.cpp

@@ -11,78 +11,57 @@ namespace anki
 
 Config::Config()
 {
-	//
 	// Renderer
-	//
+	newOption("r.renderingQuality", 1.0, "Rendering quality factor");
+	newOption("r.lodDistance0", 10.0, "Distance that will be used to calculate the LOD 0");
+	newOption("r.lodDistance1", 20.0, "Distance that will be used to calculate the LOD 1");
+	newOption("r.clusterSizeX", 32);
+	newOption("r.clusterSizeY", 26);
+	newOption("r.clusterSizeZ", 32);
+	newOption("r.maxLightsPerCluster", 8);
 
-	newOption("sm.enabled", true);
-	newOption("sm.poissonEnabled", true);
-	newOption("sm.bilinearEnabled", true);
-	newOption("sm.resolution", 512);
-	newOption("sm.maxLights", 4);
+	newOption("r.shadowMapping.enabled", true);
+	newOption("r.shadowMapping.poissonEnabled", true);
+	newOption("r.shadowMapping.bilinearEnabled", true);
+	newOption("r.shadowMapping.resolution", 512);
+	newOption("r.shadowMapping.maxLights", 16);
 
-	newOption("r.ms.earlyZDistance", 10.0);
+	newOption("r.lensFlare.maxSpritesPerFlare", 8);
+	newOption("r.lensFlare.maxFlares", 16);
 
-	newOption("is.maxPointLights", 384);
-	newOption("is.maxSpotLights", 16);
-	newOption("is.maxSpotTexLights", 8);
-	newOption("is.maxLightsPerCluster", 8);
+	newOption("r.bloom.threshold", 2.5);
+	newOption("r.bloom.scale", 2.5);
 
-	newOption("lf.maxSpritesPerFlare", 8);
-	newOption("lf.maxFlares", 16);
+	newOption("r.finalComposite.sharpen", false);
 
-	newOption("bloom.enabled", true);
-	newOption("bloom.renderingQuality", 0.5);
-	newOption("bloom.blurringDist", 1.0);
-	newOption("bloom.samples", 5);
-	newOption("bloom.blurringIterationsCount", 1);
-	newOption("bloom.threshold", 1.0);
-	newOption("bloom.scale", 2.0);
+	newOption("r.indirect.reflectionSize", 128);
+	newOption("r.indirect.maxProbeCount", 32);
 
-	newOption("ssao.enabled", true);
-	newOption("ssao.renderingQuality", 0.3);
-	newOption("ssao.blurringIterationsCount", 1);
+	newOption("r.dbg.enabled", false);
 
-	newOption("sslf.enabled", true);
-
-	newOption("pps.enabled", true);
-	newOption("pps.sharpen", false);
-	newOption("pps.gammaCorrection", true);
-
-	newOption("ir.enabled", true);
-	newOption("ir.rendererSize", 128);
-	newOption("ir.cubemapTextureArraySize", 32);
-	newOption("sslr.enabled", true);
-	newOption("sslr.startRoughnes", 0.2);
-
-	newOption("dbg.enabled", false);
-	newOption("tm.enabled", true);
+	// Scene
+	newOption("scene.imageReflectionMaxDistance", 30.0);
+	newOption("scene.earlyZDistance", 10.0, "Objects with distance lower than that will be used in early Z");
 
 	// Globals
 	newOption("width", 1280);
 	newOption("height", 768);
-	newOption("renderingQuality", 1.0); // Applies only to MainRenderer
-	newOption("lodDistance", 10.0); // Distance that used to calculate the LOD
 	newOption("tessellation", true);
-	newOption("clusterSizeX", 32);
-	newOption("clusterSizeY", 26);
-	newOption("clusterSizeZ", 32);
-	newOption("imageReflectionMaxDistance", 30.0);
 	newOption("clearCaches", false);
 
 	// Resource
 	newOption("rsrc.maxTextureSize", 1024 * 1024);
 	newOption("rsrc.textureAnisotropy", 8);
-	newOption("rsrc.dataPaths", ".");
+	newOption("rsrc.dataPaths", ".", "The engine loads assets only in from these paths. Separate them with :");
 	newOption("rsrc.transferScratchMemorySize", 256_MB);
 
 	// Window
-	newOption("glmajor", 4);
-	newOption("glminor", 5);
-	newOption("fullscreenDesktopResolution", false);
-	newOption("debugContext", false);
-	newOption("vsync", false);
-	newOption("debugMarkers", false);
+	newOption("window.glmajor", 4);
+	newOption("window.glminor", 5);
+	newOption("window.fullscreenDesktopResolution", false);
+	newOption("window.debugContext", false);
+	newOption("window.vsync", false);
+	newOption("window.debugMarkers", false);
 
 	// GR
 	newOption("gr.diskShaderCacheMaxSize", 10_MB);

+ 1 - 1
src/anki/gr/gl/GlState.cpp

@@ -83,7 +83,7 @@ __stdcall
 
 void GlState::initMainThread(const ConfigSet& config)
 {
-	m_registerMessages = config.getNumber("debugContext");
+	m_registerMessages = config.getNumber("window.debugContext");
 }
 
 void GlState::initRenderThread()

+ 1 - 1
src/anki/gr/gl/GrManagerImpl.cpp

@@ -37,7 +37,7 @@ GrAllocator<U8> GrManagerImpl::getAllocator() const
 
 Error GrManagerImpl::init(GrManagerInitInfo& init)
 {
-	m_debugMarkers = init.m_config->getNumber("debugMarkers");
+	m_debugMarkers = init.m_config->getNumber("window.debugMarkers");
 
 	// Init the backend of the backend
 	ANKI_CHECK(createBackend(init));

+ 5 - 5
src/anki/gr/gl/GrManagerImplSdl.cpp

@@ -36,10 +36,10 @@ public:
 		m_window = init.m_window->getNative().m_window;
 
 		ANKI_GL_LOGI("Creating GL %u.%u context...",
-			U(init.m_config->getNumber("glmajor")),
-			U(init.m_config->getNumber("glminor")));
+			U(init.m_config->getNumber("window.glmajor")),
+			U(init.m_config->getNumber("window.glminor")));
 
-		if(init.m_config->getNumber("debugContext"))
+		if(init.m_config->getNumber("window.debugContext"))
 		{
 			if(SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG))
 			{
@@ -48,8 +48,8 @@ public:
 			}
 		}
 
-		if(SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, init.m_config->getNumber("glmajor"))
-			|| SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, init.m_config->getNumber("glminor"))
+		if(SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, init.m_config->getNumber("window.glmajor"))
+			|| SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, init.m_config->getNumber("window.glminor"))
 			|| SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE))
 		{
 			ANKI_GL_LOGE("SDL_GL_SetAttribute() failed");

+ 3 - 3
src/anki/gr/vulkan/GrManagerImpl.cpp

@@ -237,7 +237,7 @@ Error GrManagerImpl::initInstance(const GrManagerInitInfo& init)
 		"VK_LAYER_LUNARG_object_tracker",
 		"VK_LAYER_LUNARG_standard_validation"}};
 	Array<const char*, LAYERS.getSize()> layersToEnable; // Keep it alive in the stack
-	if(init.m_config->getNumber("debugContext"))
+	if(init.m_config->getNumber("window.debugContext"))
 	{
 		uint32_t count;
 		vkEnumerateInstanceLayerProperties(&count, nullptr);
@@ -462,7 +462,7 @@ Error GrManagerImpl::initDevice(const GrManagerInitInfo& init)
 				// Don't add it just yet. Can't enable it at the same time with VK_KHR_maintenance1
 			}
 			else if(CString(extensionInfos[extCount].extensionName) == VK_EXT_DEBUG_MARKER_EXTENSION_NAME
-				&& init.m_config->getNumber("debugMarkers"))
+				&& init.m_config->getNumber("window.debugMarkers"))
 			{
 				m_extensions |= VulkanExtensions::EXT_DEBUG_MARKER;
 				extensionsToEnable[extensionsToEnableCount++] = VK_EXT_DEBUG_MARKER_EXTENSION_NAME;
@@ -565,7 +565,7 @@ Error GrManagerImpl::initSwapchain(const GrManagerInitInfo& init)
 	vkGetPhysicalDeviceSurfacePresentModesKHR(m_physicalDevice, m_surface, &presentModeCount, &presentModes[0]);
 
 	VkPresentModeKHR presentMode = VK_PRESENT_MODE_MAX_ENUM_KHR;
-	if(init.m_config->getNumber("vsync"))
+	if(init.m_config->getNumber("window.vsync"))
 	{
 		ANKI_VK_LOGI("vsync is on");
 		presentMode = VK_PRESENT_MODE_FIFO_KHR;

+ 19 - 5
src/anki/misc/ConfigSet.cpp

@@ -22,6 +22,7 @@ ConfigSet::~ConfigSet()
 	{
 		o.m_name.destroy(m_alloc);
 		o.m_strVal.destroy(m_alloc);
+		o.m_helpMsg.destroy(m_alloc);
 	}
 
 	m_options.destroy(m_alloc);
@@ -76,7 +77,7 @@ const ConfigSet::Option* ConfigSet::tryFind(const CString& name) const
 	return nullptr;
 }
 
-void ConfigSet::newOption(const CString& name, const CString& value)
+void ConfigSet::newOption(const CString& name, const CString& value, const CString& helpMsg)
 {
 	ANKI_ASSERT(!tryFind(name));
 
@@ -84,11 +85,15 @@ void ConfigSet::newOption(const CString& name, const CString& value)
 	o.m_name.create(m_alloc, name);
 	o.m_strVal.create(m_alloc, value);
 	o.m_type = 0;
+	if(!helpMsg.isEmpty())
+	{
+		o.m_helpMsg.create(m_alloc, helpMsg);
+	}
 
 	m_options.emplaceBack(m_alloc, std::move(o));
 }
 
-void ConfigSet::newOption(const CString& name, F64 value)
+void ConfigSet::newOption(const CString& name, F64 value, const CString& helpMsg)
 {
 	ANKI_ASSERT(!tryFind(name));
 
@@ -96,6 +101,10 @@ void ConfigSet::newOption(const CString& name, F64 value)
 	o.m_name.create(m_alloc, name);
 	o.m_fVal = value;
 	o.m_type = 1;
+	if(!helpMsg.isEmpty())
+	{
+		o.m_helpMsg.create(m_alloc, helpMsg);
+	}
 
 	m_options.emplaceBack(m_alloc, std::move(o));
 }
@@ -191,10 +200,14 @@ Error ConfigSet::saveToFile(CString filename) const
 
 	for(const Option& option : m_options)
 	{
+		if(!option.m_helpMsg.isEmpty())
+		{
+			ANKI_CHECK(file.writeText("\t<!-- %s -->\n", &option.m_helpMsg[0]));
+		}
+
 		if(option.m_type == 1)
 		{
-			// Some of the options are integers so try not to make them appear
-			// as floats in the file
+			// Some of the options are integers so try not to make them appear as floats in the file
 			if(option.m_fVal == round(option.m_fVal) && option.m_fVal >= 0.0)
 			{
 				ANKI_CHECK(file.writeText("\t<%s>%u</%s>\n", &option.m_name[0], U(option.m_fVal), &option.m_name[0]));
@@ -206,7 +219,8 @@ Error ConfigSet::saveToFile(CString filename) const
 		}
 		else
 		{
-			ANKI_CHECK(file.writeText("\t<%s>%s</%s>\n", &option.m_name[0], &option.m_strVal[0], &option.m_name[0]));
+			ANKI_CHECK(file.writeText(
+				"\t<%s><![CDATA[%s]]></%s>\n", &option.m_name[0], &option.m_strVal[0], &option.m_name[0]));
 		}
 	}
 

+ 4 - 2
src/anki/misc/ConfigSet.h

@@ -51,8 +51,8 @@ public:
 	ANKI_USE_RESULT Error setFromCommandLineArguments(U cmdLineArgsCount, char* cmdLineArgs[]);
 
 protected:
-	void newOption(const CString& name, const CString& value);
-	void newOption(const CString& name, F64 value);
+	void newOption(const CString& name, const CString& value, const CString& helpMsg = "");
+	void newOption(const CString& name, F64 value, const CString& helpMsg = "");
 
 private:
 	class Option : public NonCopyable
@@ -60,6 +60,7 @@ private:
 	public:
 		String m_name;
 		String m_strVal;
+		String m_helpMsg;
 		F64 m_fVal = 0.0;
 		U8 m_type = 0; ///< 0: string, 1: float
 
@@ -68,6 +69,7 @@ private:
 		Option(Option&& b)
 			: m_name(std::move(b.m_name))
 			, m_strVal(std::move(b.m_strVal))
+			, m_helpMsg(std::move(b.m_helpMsg))
 			, m_fVal(b.m_fVal)
 			, m_type(b.m_type)
 		{

+ 2 - 2
src/anki/renderer/Bloom.cpp

@@ -24,8 +24,8 @@ Error BloomExposure::init(const ConfigSet& config)
 	m_width = m_r->getDownscaleBlur().getSmallPassWidth() * 2;
 	m_height = m_r->getDownscaleBlur().getSmallPassHeight() * 2;
 
-	m_threshold = config.getNumber("bloom.threshold");
-	m_scale = config.getNumber("bloom.scale");
+	m_threshold = config.getNumber("r.bloom.threshold");
+	m_scale = config.getNumber("r.bloom.scale");
 
 	// Create RT
 	m_rt = m_r->createAndClearRenderTarget(m_r->create2DRenderTargetInitInfo(m_width,

+ 1 - 1
src/anki/renderer/Dbg.cpp

@@ -35,7 +35,7 @@ Dbg::~Dbg()
 
 Error Dbg::init(const ConfigSet& initializer)
 {
-	m_enabled = initializer.getNumber("dbg.enabled");
+	m_enabled = initializer.getNumber("r.dbg.enabled");
 	m_flags.set(DbgFlag::ALL);
 	return ErrorCode::NONE;
 }

+ 1 - 2
src/anki/renderer/Drawer.cpp

@@ -92,8 +92,7 @@ void RenderableDrawer::drawSingle(DrawContext& ctx)
 
 	const RenderableQueueElement& rqel = *ctx.m_renderableElement;
 
-	const F32 flod = min<F32>(m_r->calculateLod(rqel.m_distanceFromCamera), MAX_LOD_COUNT - 1);
-	const U8 lod = U8(flod);
+	const U8 lod = min<U8>(m_r->calculateLod(rqel.m_distanceFromCamera), MAX_LOD_COUNT - 1);
 
 	const Bool shouldFlush = ctx.m_cachedRenderElementCount > 0
 		&& (!canMergeRenderableQueueElements(ctx.m_cachedRenderElements[ctx.m_cachedRenderElementCount - 1], rqel)

+ 1 - 1
src/anki/renderer/FinalComposite.cpp

@@ -36,7 +36,7 @@ Error FinalComposite::initInternal(const ConfigSet& config)
 	ANKI_ASSERT("Initializing PPS");
 
 	ANKI_CHECK(loadColorGradingTexture("engine_data/DefaultLut.ankitex"));
-	m_sharpenEnabled = config.getNumber("pps.sharpen");
+	m_sharpenEnabled = config.getNumber("r.finalComposite.sharpen");
 
 	if(!m_r->getDrawToDefaultFramebuffer())
 	{

+ 46 - 8
src/anki/renderer/GBuffer.cpp

@@ -104,7 +104,8 @@ void GBuffer::buildCommandBuffers(RenderingContext& ctx, U threadId, U threadCou
 	ANKI_TRACE_SCOPED_EVENT(RENDER_MS);
 
 	// Get some stuff
-	const U problemSize = ctx.m_renderQueue->m_renderables.getSize();
+	const PtrSize earlyZCount = ctx.m_renderQueue->m_earlyZRenderables.getSize();
+	const U problemSize = ctx.m_renderQueue->m_renderables.getSize() + earlyZCount;
 	PtrSize start, end;
 	ThreadPoolTask::choseStartEnd(threadId, threadCount, problemSize, start, end);
 
@@ -131,13 +132,50 @@ void GBuffer::buildCommandBuffers(RenderingContext& ctx, U threadId, U threadCou
 		// Set some state, leave the rest to default
 		cmdb->setViewport(0, 0, m_r->getWidth(), m_r->getHeight());
 
-		// Start drawing
-		m_r->getSceneDrawer().drawRange(Pass::GB_FS,
-			ctx.m_renderQueue->m_viewMatrix,
-			ctx.m_viewProjMatJitter,
-			cmdb,
-			ctx.m_renderQueue->m_renderables.getBegin() + start,
-			ctx.m_renderQueue->m_renderables.getBegin() + end);
+		const I32 earlyZStart = max(I32(start), 0);
+		const I32 earlyZEnd = min(I32(end), I32(earlyZCount));
+		const I32 colorStart = max(I32(start) - I32(earlyZCount), 0);
+		const I32 colorEnd = I32(end) - I32(earlyZCount);
+
+		// First do early Z (if needed)
+		if(earlyZStart < earlyZEnd)
+		{
+			for(U i = 0; i < GBUFFER_COLOR_ATTACHMENT_COUNT; ++i)
+			{
+				cmdb->setColorChannelWriteMask(i, ColorBit::NONE);
+			}
+
+			ANKI_ASSERT(earlyZStart < earlyZEnd && earlyZEnd <= I32(earlyZCount));
+			m_r->getSceneDrawer().drawRange(Pass::SM,
+				ctx.m_renderQueue->m_viewMatrix,
+				ctx.m_viewProjMatJitter,
+				cmdb,
+				ctx.m_renderQueue->m_earlyZRenderables.getBegin() + earlyZStart,
+				ctx.m_renderQueue->m_earlyZRenderables.getBegin() + earlyZEnd);
+
+			// Restore state for the color write
+			if(colorStart < colorEnd)
+			{
+				for(U i = 0; i < GBUFFER_COLOR_ATTACHMENT_COUNT; ++i)
+				{
+					cmdb->setColorChannelWriteMask(i, ColorBit::ALL);
+				}
+			}
+		}
+
+		// Do the color writes
+		if(colorStart < colorEnd)
+		{
+			cmdb->setDepthCompareOperation(CompareOperation::LESS_EQUAL);
+
+			ANKI_ASSERT(colorStart < colorEnd && colorEnd <= I32(ctx.m_renderQueue->m_renderables.getSize()));
+			m_r->getSceneDrawer().drawRange(Pass::GB_FS,
+				ctx.m_renderQueue->m_viewMatrix,
+				ctx.m_viewProjMatJitter,
+				cmdb,
+				ctx.m_renderQueue->m_renderables.getBegin() + colorStart,
+				ctx.m_renderQueue->m_renderables.getBegin() + colorEnd);
+		}
 	}
 }
 

+ 2 - 2
src/anki/renderer/Indirect.cpp

@@ -65,7 +65,7 @@ Error Indirect::init(const ConfigSet& config)
 
 Error Indirect::initInternal(const ConfigSet& config)
 {
-	m_fbSize = config.getNumber("ir.rendererSize");
+	m_fbSize = config.getNumber("r.indirect.reflectionSize");
 
 	if(m_fbSize < TILE_SIZE)
 	{
@@ -73,7 +73,7 @@ Error Indirect::initInternal(const ConfigSet& config)
 		return ErrorCode::USER_DATA;
 	}
 
-	m_cubemapArrSize = config.getNumber("ir.cubemapTextureArraySize");
+	m_cubemapArrSize = config.getNumber("r.indirect.maxProbeCount");
 
 	if(m_cubemapArrSize < 2)
 	{

+ 2 - 2
src/anki/renderer/LensFlare.cpp

@@ -52,8 +52,8 @@ Error LensFlare::initInternal(const ConfigSet& config)
 
 Error LensFlare::initSprite(const ConfigSet& config)
 {
-	m_maxSpritesPerFlare = config.getNumber("lf.maxSpritesPerFlare");
-	m_maxFlares = config.getNumber("lf.maxFlares");
+	m_maxSpritesPerFlare = config.getNumber("r.lensFlare.maxSpritesPerFlare");
+	m_maxFlares = config.getNumber("r.lensFlare.maxFlares");
 
 	if(m_maxSpritesPerFlare < 1 || m_maxFlares < 1)
 	{

+ 4 - 4
src/anki/renderer/LightShading.cpp

@@ -69,7 +69,7 @@ Error LightShading::init(const ConfigSet& config)
 
 Error LightShading::initInternal(const ConfigSet& config)
 {
-	m_maxLightIds = config.getNumber("is.maxLightsPerCluster");
+	m_maxLightIds = config.getNumber("r.maxLightsPerCluster");
 
 	if(m_maxLightIds == 0)
 	{
@@ -77,9 +77,9 @@ Error LightShading::initInternal(const ConfigSet& config)
 		return ErrorCode::USER_DATA;
 	}
 
-	m_clusterCounts[0] = config.getNumber("clusterSizeX");
-	m_clusterCounts[1] = config.getNumber("clusterSizeY");
-	m_clusterCounts[2] = config.getNumber("clusterSizeZ");
+	m_clusterCounts[0] = config.getNumber("r.clusterSizeX");
+	m_clusterCounts[1] = config.getNumber("r.clusterSizeY");
+	m_clusterCounts[2] = config.getNumber("r.clusterSizeZ");
 	m_clusterCount = m_clusterCounts[0] * m_clusterCounts[1] * m_clusterCounts[2];
 
 	m_maxLightIds *= m_clusterCount;

+ 1 - 1
src/anki/renderer/MainRenderer.cpp

@@ -54,7 +54,7 @@ Error MainRenderer::create(ThreadPool* threadpool,
 
 	// Init renderer and manipulate the width/height
 	ConfigSet config2 = config;
-	m_renderingQuality = config.getNumber("renderingQuality");
+	m_renderingQuality = config.getNumber("r.renderingQuality");
 	UVec2 size(m_renderingQuality * F32(m_width), m_renderingQuality * F32(m_height));
 
 	config2.set("width", size.x());

+ 13 - 3
src/anki/renderer/RenderQueue.h

@@ -24,6 +24,7 @@ public:
 	Mat4 m_viewProjectionMatrix;
 };
 
+/// Context that contains variables for drawing and will be passed to RenderableQueueElementDrawCallback.
 class RenderQueueDrawContext final : public RenderingMatrices
 {
 public:
@@ -32,9 +33,10 @@ public:
 	StagingGpuMemoryManager* m_stagingGpuAllocator ANKI_DBG_NULLIFY;
 };
 
-/// Draw callback.
+/// Draw callback for the RenderableQueueElement.
 using RenderableQueueElementDrawCallback = void (*)(RenderQueueDrawContext& ctx, WeakArray<const void*> userData);
 
+/// Render queue element that contains info on items that populate the G-buffer or the forward shading buffer etc.
 class RenderableQueueElement final
 {
 public:
@@ -47,6 +49,7 @@ public:
 static_assert(
 	std::is_trivially_destructible<RenderableQueueElement>::value == true, "Should be trivially destructible");
 
+/// Point light render queue element.
 class PointLightQueueElement final
 {
 public:
@@ -67,6 +70,7 @@ public:
 static_assert(
 	std::is_trivially_destructible<PointLightQueueElement>::value == true, "Should be trivially destructible");
 
+/// Spot light render queue element.
 class SpotLightQueueElement final
 {
 public:
@@ -89,11 +93,12 @@ public:
 
 static_assert(std::is_trivially_destructible<SpotLightQueueElement>::value == true, "Should be trivially destructible");
 
-/// Normally the visibility tests don't perform tests on the reflection probes because probes dont's change that often.
+/// Normally the visibility tests don't perform tests on the reflection probes because probes dont change that often.
 /// This callback will be used by the renderer to inform a reflection probe that on the next frame it will be rendererd.
 /// In that case the probe should fill the render queues.
 using ReflectionProbeQueueElementFeedbackCallback = void (*)(Bool fillRenderQueuesOnNextFrame, void* userData);
 
+/// Reflection probe render queue element.
 class ReflectionProbeQueueElement final
 {
 public:
@@ -109,6 +114,7 @@ public:
 static_assert(
 	std::is_trivially_destructible<ReflectionProbeQueueElement>::value == true, "Should be trivially destructible");
 
+/// Lens flare render queue element.
 class LensFlareQueueElement final
 {
 public:
@@ -120,6 +126,7 @@ public:
 
 static_assert(std::is_trivially_destructible<LensFlareQueueElement>::value == true, "Should be trivially destructible");
 
+/// Decal render queue element.
 class DecalQueueElement final
 {
 public:
@@ -137,11 +144,12 @@ public:
 
 static_assert(std::is_trivially_destructible<DecalQueueElement>::value == true, "Should be trivially destructible");
 
-/// The render queue.
+/// The render queue. This is what the renderer is fed to render.
 class RenderQueue : public RenderingMatrices
 {
 public:
 	WeakArray<RenderableQueueElement> m_renderables; ///< Deferred shading or shadow renderables.
+	WeakArray<RenderableQueueElement> m_earlyZRenderables; ///< Some renderables that will be used for Early Z pass.
 	WeakArray<RenderableQueueElement> m_forwardShadingRenderables;
 	WeakArray<PointLightQueueElement> m_pointLights;
 	WeakArray<PointLightQueueElement*> m_shadowPointLights; ///< Points to elements in m_pointLights.
@@ -157,6 +165,8 @@ public:
 	F32 m_cameraNear;
 	F32 m_cameraFar;
 };
+
+static_assert(std::is_trivially_destructible<RenderQueue>::value == true, "Should be trivially destructible");
 /// @}
 
 } // end namespace anki

+ 8 - 4
src/anki/renderer/Renderer.cpp

@@ -79,7 +79,9 @@ Error Renderer::initInternal(const ConfigSet& config)
 	m_height = config.getNumber("height");
 	ANKI_R_LOGI("Initializing offscreen renderer. Size %ux%u", m_width, m_height);
 
-	m_lodDistance = config.getNumber("lodDistance");
+	ANKI_ASSERT(m_lodDistances.getSize() == 2);
+	m_lodDistances[0] = config.getNumber("r.lodDistance0");
+	m_lodDistances[1] = config.getNumber("r.lodDistance1");
 	m_frameCount = 0;
 
 	m_tessellation = config.getNumber("tessellation");
@@ -497,7 +499,7 @@ TexturePtr Renderer::createAndClearRenderTarget(const TextureInitInfo& inf)
 
 void Renderer::buildCommandBuffersInternal(RenderingContext& ctx, U32 threadId, PtrSize threadCount)
 {
-	// MS
+	// G-Buffer pass
 	//
 	m_gbuffer->buildCommandBuffers(ctx, threadId, threadCount);
 
@@ -566,10 +568,12 @@ Error Renderer::buildCommandBuffers(RenderingContext& ctx)
 	// Find the last jobs for MS and FS
 	U32 lastMsJob = MAX_U32;
 	U32 lastFsJob = MAX_U32;
-	U threadCount = threadPool.getThreadsCount();
+	const U threadCount = threadPool.getThreadsCount();
 	for(U i = threadCount - 1; i != 0; --i)
 	{
-		if(threadWillDoWork(ctx.m_renderQueue->m_renderables.getSize(), i, threadCount) && lastMsJob == MAX_U32)
+		const U gbuffProblemSize =
+			ctx.m_renderQueue->m_renderables.getSize() + ctx.m_renderQueue->m_earlyZRenderables.getSize();
+		if(threadWillDoWork(gbuffProblemSize, i, threadCount) && lastMsJob == MAX_U32)
 		{
 			lastMsJob = i;
 		}

+ 15 - 3
src/anki/renderer/Renderer.h

@@ -272,9 +272,21 @@ anki_internal:
 	}
 
 	/// Get the LOD given the distance of an object from the camera
-	F32 calculateLod(F32 distance) const
+	U calculateLod(F32 distance) const
 	{
-		return distance / m_lodDistance;
+		ANKI_ASSERT(m_lodDistances.getSize() == 2);
+		if(distance < m_lodDistances[0])
+		{
+			return 0;
+		}
+		else if(distance < m_lodDistances[1])
+		{
+			return 1;
+		}
+		else
+		{
+			return 2;
+		}
 	}
 
 	/// Create the init info for a 2D texture that will be used as a render target.
@@ -396,7 +408,7 @@ private:
 	U32 m_width;
 	U32 m_height;
 
-	F32 m_lodDistance; ///< Distance that used to calculate the LOD
+	Array<F32, MAX_LOD_COUNT - 1> m_lodDistances; ///< Distance that used to calculate the LOD
 	Bool8 m_tessellation;
 
 	RenderableDrawer m_sceneDrawer;

+ 6 - 6
src/anki/renderer/ShadowMapping.cpp

@@ -37,9 +37,9 @@ Error ShadowMapping::init(const ConfigSet& config)
 
 Error ShadowMapping::initInternal(const ConfigSet& config)
 {
-	m_poissonEnabled = config.getNumber("sm.poissonEnabled");
-	m_bilinearEnabled = config.getNumber("sm.bilinearEnabled");
-	m_resolution = config.getNumber("sm.resolution");
+	m_poissonEnabled = config.getNumber("r.shadowMapping.poissonEnabled");
+	m_bilinearEnabled = config.getNumber("r.shadowMapping.bilinearEnabled");
+	m_resolution = config.getNumber("r.shadowMapping.resolution");
 
 	//
 	// Init the shadowmaps
@@ -53,7 +53,7 @@ Error ShadowMapping::initInternal(const ConfigSet& config)
 	sminit.m_type = TextureType::_2D_ARRAY;
 	sminit.m_width = m_resolution;
 	sminit.m_height = m_resolution;
-	sminit.m_layerCount = config.getNumber("sm.maxLights");
+	sminit.m_layerCount = config.getNumber("r.shadowMapping.maxLights");
 	sminit.m_depth = 1;
 	sminit.m_format = DEPTH_RT_PIXEL_FORMAT;
 	sminit.m_mipmapsCount = 1;
@@ -66,7 +66,7 @@ Error ShadowMapping::initInternal(const ConfigSet& config)
 	m_omniTexArray = m_r->createAndClearRenderTarget(sminit);
 
 	// Init 2D layers
-	m_spots.create(getAllocator(), config.getNumber("sm.maxLights"));
+	m_spots.create(getAllocator(), config.getNumber("r.shadowMapping.maxLights"));
 
 	FramebufferInitInfo fbInit("shadows");
 	fbInit.m_depthStencilAttachment.m_texture = m_spotTexArray;
@@ -85,7 +85,7 @@ Error ShadowMapping::initInternal(const ConfigSet& config)
 	}
 
 	// Init cube layers
-	m_omnis.create(getAllocator(), config.getNumber("sm.maxLights"));
+	m_omnis.create(getAllocator(), config.getNumber("r.shadowMapping.maxLights"));
 
 	fbInit.m_depthStencilAttachment.m_texture = m_omniTexArray;
 

+ 1 - 0
src/anki/renderer/Ssao.cpp

@@ -57,6 +57,7 @@ void SsaoMain::run(RenderingContext& ctx)
 	cmdb->bindTexture(0, 0, m_r->getDepthDownscale().m_qd.m_depthRt);
 	cmdb->bindTextureAndSampler(0, 1, m_r->getGBuffer().m_rt2, m_r->getLinearSampler());
 	cmdb->bindTexture(0, 2, m_noiseTex->getGrTexture());
+	cmdb->informTextureCurrentUsage(m_ssao->m_rt[(m_r->getFrameCount() + 1) & 1], TextureUsageBit::SAMPLED_FRAGMENT);
 	cmdb->bindTexture(0, 3, m_ssao->m_rt[(m_r->getFrameCount() + 1) & 1]);
 
 	struct Unis

+ 2 - 1
src/anki/scene/Camera.cpp

@@ -78,7 +78,8 @@ Error Camera::init(Frustum* frustum)
 		| FrustumComponentVisibilityTestFlag::REFLECTION_PROBES
 		| FrustumComponentVisibilityTestFlag::REFLECTION_PROXIES
 		| FrustumComponentVisibilityTestFlag::OCCLUDERS
-		| FrustumComponentVisibilityTestFlag::DECALS);
+		| FrustumComponentVisibilityTestFlag::DECALS
+		| FrustumComponentVisibilityTestFlag::EARLY_Z);
 
 	// Feedback component #2
 	newComponent<CameraFrustumFeedbackComponent>(this);

+ 5 - 3
src/anki/scene/FrustumComponent.h

@@ -19,7 +19,7 @@ namespace anki
 
 /// Flags that affect visibility tests.
 /// WARNING!!!!!!!!!!: If you change this remember to change the FrustumComponent::Flags as well
-enum class FrustumComponentVisibilityTestFlag : U8
+enum class FrustumComponentVisibilityTestFlag : U16
 {
 	NONE = 0,
 	RENDER_COMPONENTS = 1 << 0,
@@ -30,10 +30,12 @@ enum class FrustumComponentVisibilityTestFlag : U8
 	REFLECTION_PROXIES = 1 << 5,
 	OCCLUDERS = 1 << 6,
 	DECALS = 1 << 7,
+	EARLY_Z = 1 << 8,
 
 	ALL_TESTS = RENDER_COMPONENTS | LIGHT_COMPONENTS | LENS_FLARE_COMPONENTS | SHADOW_CASTERS | REFLECTION_PROBES
 		| REFLECTION_PROXIES
 		| DECALS
+		| EARLY_Z
 };
 ANKI_ENUM_ALLOW_NUMERIC_OPERATIONS(FrustumComponentVisibilityTestFlag, inline)
 
@@ -143,8 +145,8 @@ public:
 private:
 	enum Flags
 	{
-		SHAPE_MARKED_FOR_UPDATE = 1 << 8,
-		TRANSFORM_MARKED_FOR_UPDATE = 1 << 9,
+		SHAPE_MARKED_FOR_UPDATE = 1 << 9,
+		TRANSFORM_MARKED_FOR_UPDATE = 1 << 10,
 	};
 	ANKI_ENUM_ALLOW_NUMERIC_OPERATIONS(Flags, friend)
 

+ 123 - 22
src/anki/scene/ModelNode.cpp

@@ -16,7 +16,7 @@ namespace anki
 {
 
 /// Render component implementation.
-class ModelPatchRenderComponent : public RenderComponent
+class ModelPatchNode::MRenderComponent : public RenderComponent
 {
 public:
 	const ModelPatchNode& getNode() const
@@ -24,7 +24,7 @@ public:
 		return static_cast<const ModelPatchNode&>(getSceneNode());
 	}
 
-	ModelPatchRenderComponent(ModelPatchNode* node)
+	MRenderComponent(ModelPatchNode* node)
 		: RenderComponent(node, node->m_modelPatch->getMaterial())
 	{
 	}
@@ -54,7 +54,7 @@ Error ModelPatchNode::init(const ModelPatch* modelPatch, U idx, const ModelNode&
 	newComponent<SpatialComponent>(this, &m_obb);
 
 	// Render component
-	newComponent<ModelPatchRenderComponent>(this);
+	newComponent<MRenderComponent>(this);
 
 	// Merge key
 	Array<U64, 2> toHash;
@@ -147,6 +147,20 @@ public:
 	}
 };
 
+class ModelNode::MRenderComponent : public RenderComponent
+{
+public:
+	MRenderComponent(ModelNode* node)
+		: RenderComponent(node, node->m_model->getModelPatches()[0]->getMaterial())
+	{
+	}
+
+	void setupRenderableQueueElement(RenderableQueueElement& el) const override
+	{
+		static_cast<const ModelNode&>(getSceneNode()).setupRenderableQueueElement(el);
+	}
+};
+
 ModelNode::ModelNode(SceneGraph* scene, CString name)
 	: SceneNode(scene, name)
 {
@@ -161,41 +175,128 @@ Error ModelNode::init(const CString& modelFname)
 {
 	ANKI_CHECK(getResourceManager().loadResource(modelFname, m_model));
 
-	m_modelPatches.create(getSceneAllocator(), m_model->getModelPatches().getSize(), nullptr);
-
-	U count = 0;
-	auto it = m_model->getModelPatches().getBegin();
-	auto end = m_model->getModelPatches().getEnd();
-	for(; it != end; it++)
+	if(m_model->getModelPatches().getSize() > 1)
 	{
-		ModelPatchNode* mpn;
-		StringAuto nname(getFrameAllocator());
-		ANKI_CHECK(getSceneGraph().newSceneNode(CString(), mpn, *it, count, *this));
+		// Multiple patches, create multiple nodes
+
+		m_modelPatches.create(getSceneAllocator(), m_model->getModelPatches().getSize(), nullptr);
 
-		m_modelPatches[count++] = mpn;
-		addChild(mpn);
+		U count = 0;
+		auto it = m_model->getModelPatches().getBegin();
+		auto end = m_model->getModelPatches().getEnd();
+		for(; it != end; it++)
+		{
+			ModelPatchNode* mpn;
+			StringAuto nname(getFrameAllocator());
+			ANKI_CHECK(getSceneGraph().newSceneNode(CString(), mpn, *it, count, *this));
+
+			m_modelPatches[count++] = mpn;
+			addChild(mpn);
+		}
+
+		// Move component
+		newComponent<MoveComponent>(this);
+
+		// Feedback component
+		newComponent<MoveFeedbackComponent>(this);
 	}
+	else
+	{
+		// Only one patch, don't need to create multiple nodes. Pack everything in this one.
 
-	// Move component
-	newComponent<MoveComponent>(this);
+		m_mergeKey = m_model->getUuid();
 
-	// Feedback component
-	newComponent<MoveFeedbackComponent>(this);
+		newComponent<MoveComponent>(this);
+		newComponent<MoveFeedbackComponent>(this);
+		newComponent<SpatialComponent>(this, &m_obb);
+		newComponent<MRenderComponent>(this);
+	}
 
 	return ErrorCode::NONE;
 }
 
 void ModelNode::onMoveComponentUpdate(const MoveComponent& move)
 {
-	// Inform the children about the moves
-	for(ModelPatchNode* child : m_modelPatches)
+	if(!isSinglePatch())
 	{
-		child->m_obb = child->m_modelPatch->getBoundingShape().getTransformed(move.getWorldTransform());
+		// Inform the children about the moves
+		for(ModelPatchNode* child : m_modelPatches)
+		{
+			child->m_obb = child->m_modelPatch->getBoundingShape().getTransformed(move.getWorldTransform());
+
+			SpatialComponent& sp = child->getComponent<SpatialComponent>();
+			sp.markForUpdate();
+			sp.setSpatialOrigin(move.getWorldTransform().getOrigin());
+		}
+	}
+	else
+	{
+		m_obb = m_model->getModelPatches()[0]->getBoundingShape().getTransformed(move.getWorldTransform());
 
-		SpatialComponent& sp = child->getComponent<SpatialComponent>();
+		SpatialComponent& sp = getComponentAt<SpatialComponent>(2);
 		sp.markForUpdate();
 		sp.setSpatialOrigin(move.getWorldTransform().getOrigin());
 	}
 }
 
+void ModelNode::drawCallback(RenderQueueDrawContext& ctx, WeakArray<const void*> userData)
+{
+	ANKI_ASSERT(userData.getSize() > 0 && userData.getSize() <= MAX_INSTANCES);
+	ANKI_ASSERT(ctx.m_key.m_instanceCount == userData.getSize());
+
+	const ModelNode& self = *static_cast<const ModelNode*>(userData[0]);
+	ANKI_ASSERT(self.isSinglePatch());
+	const ModelPatch* patch = self.m_model->getModelPatches()[0];
+
+	CommandBufferPtr& cmdb = ctx.m_commandBuffer;
+
+	// That will not work on multi-draw and instanced at the same time. Make sure that there is no multi-draw anywhere
+	ANKI_ASSERT(patch->getSubMeshesCount() == 0);
+
+	ModelRenderingInfo modelInf;
+	patch->getRenderingDataSub(ctx.m_key, WeakArray<U8>(), modelInf);
+
+	// Program
+	cmdb->bindShaderProgram(modelInf.m_program);
+
+	// Set attributes
+	for(U i = 0; i < modelInf.m_vertexAttributeCount; ++i)
+	{
+		const VertexAttributeInfo& attrib = modelInf.m_vertexAttributes[i];
+		cmdb->setVertexAttribute(i, attrib.m_bufferBinding, attrib.m_format, attrib.m_relativeOffset);
+	}
+
+	// Set vertex buffers
+	for(U i = 0; i < modelInf.m_vertexBufferBindingCount; ++i)
+	{
+		const VertexBufferBinding& binding = modelInf.m_vertexBufferBindings[i];
+		cmdb->bindVertexBuffer(i, binding.m_buffer, binding.m_offset, binding.m_stride, VertexStepRate::VERTEX);
+	}
+
+	// Index buffer
+	cmdb->bindIndexBuffer(modelInf.m_indexBuffer, 0, IndexType::U16);
+
+	// Uniforms
+	Array<Mat4, MAX_INSTANCES> trfs;
+	trfs[0] = Mat4(self.getComponentAt<MoveComponent>(0).getWorldTransform());
+	for(U i = 1; i < userData.getSize(); ++i)
+	{
+		const ModelNode& self2 = *static_cast<const ModelNode*>(userData[i]);
+		trfs[i] = Mat4(self2.getComponentAt<MoveComponent>(0).getWorldTransform());
+	}
+
+	StagingGpuMemoryToken token;
+	self.getComponentAt<RenderComponent>(3).allocateAndSetupUniforms(
+		ctx, WeakArray<const Mat4>(&trfs[0], userData.getSize()), *ctx.m_stagingGpuAllocator, token);
+	cmdb->bindUniformBuffer(0, 0, token.m_buffer, token.m_offset, token.m_range);
+
+	// Draw
+	cmdb->drawElements(PrimitiveTopology::TRIANGLES,
+		modelInf.m_indicesCountArray[0],
+		userData.getSize(),
+		modelInf.m_indicesOffsetArray[0] / sizeof(U16),
+		0,
+		0);
+}
+
 } // end namespace anki

+ 22 - 2
src/anki/scene/ModelNode.h

@@ -27,7 +27,6 @@ class ModelNode;
 class ModelPatchNode : public SceneNode
 {
 	friend class ModelNode;
-	friend class ModelPatchRenderComponent;
 
 public:
 	ModelPatchNode(SceneGraph* scene, CString name);
@@ -37,6 +36,8 @@ public:
 	ANKI_USE_RESULT Error init(const ModelPatch* modelPatch, U idx, const ModelNode& parent);
 
 private:
+	class MRenderComponent;
+
 	Obb m_obb; ///< In world space. ModelNode will update it.
 	const ModelPatch* m_modelPatch = nullptr; ///< The resource
 	U64 m_mergeKey = 0;
@@ -51,7 +52,7 @@ private:
 	static void drawCallback(RenderQueueDrawContext& ctx, WeakArray<const void*> userData);
 };
 
-/// The model scene node
+/// The model scene node.
 class ModelNode : public SceneNode
 {
 	friend class ModelPatchNode;
@@ -70,11 +71,30 @@ public:
 
 private:
 	class MoveFeedbackComponent;
+	class MRenderComponent;
 
 	ModelResourcePtr m_model; ///< The resource
 	DynamicArray<ModelPatchNode*> m_modelPatches;
 
+	Obb m_obb;
+	U64 m_mergeKey = 0;
+
+	Bool isSinglePatch() const
+	{
+		return m_modelPatches.getSize() == 0;
+	}
+
 	void onMoveComponentUpdate(const MoveComponent& move);
+
+	static void drawCallback(RenderQueueDrawContext& ctx, WeakArray<const void*> userData);
+
+	void setupRenderableQueueElement(RenderableQueueElement& el) const
+	{
+		ANKI_ASSERT(isSinglePatch());
+		el.m_callback = drawCallback;
+		el.m_userData = this;
+		el.m_mergeKey = m_mergeKey;
+	}
 };
 /// @}
 

+ 3 - 1
src/anki/scene/SceneGraph.cpp

@@ -86,11 +86,13 @@ Error SceneGraph::init(AllocAlignedCallback allocCb,
 	m_alloc = SceneAllocator<U8>(allocCb, allocCbData, 1024 * 10, 1.0, 0);
 	m_frameAlloc = SceneFrameAllocator<U8>(allocCb, allocCbData, 1 * 1024 * 1024);
 
+	m_earlyZDist = config.getNumber("scene.earlyZDistance");
+
 	ANKI_CHECK(m_events.create(this));
 
 	m_sectors = m_alloc.newInstance<SectorGroup>(this);
 
-	m_maxReflectionProxyDistance = config.getNumber("imageReflectionMaxDistance");
+	m_maxReflectionProxyDistance = config.getNumber("scene.imageReflectionMaxDistance");
 
 	// Init the default main camera
 	ANKI_CHECK(newSceneNode<PerspectiveCamera>("mainCamera", m_defaultMainCam));

+ 7 - 0
src/anki/scene/SceneGraph.h

@@ -210,6 +210,11 @@ anki_internal:
 		return m_componentLists;
 	}
 
+	F32 getEarlyZDistance() const
+	{
+		return m_earlyZDist;
+	}
+
 private:
 	const Timestamp* m_globalTimestamp = nullptr;
 	Timestamp m_timestamp = 0; ///< Cached timestamp
@@ -245,6 +250,8 @@ private:
 
 	SceneComponentLists m_componentLists;
 
+	F32 m_earlyZDist = -1.0;
+
 	/// Put a node in the appropriate containers
 	ANKI_USE_RESULT Error registerNode(SceneNode* node);
 	void unregisterNode(SceneNode* node);

+ 26 - 9
src/anki/scene/Visibility.cpp

@@ -222,23 +222,28 @@ void VisibilityTestTask::test(ThreadHive& hive)
 	SceneNode& testedNode = testedFrc.getSceneNode();
 	auto alloc = m_visCtx->m_scene->getFrameAllocator();
 
-	Bool wantsRenderComponents =
+	const Bool wantsRenderComponents =
 		testedFrc.visibilityTestsEnabled(FrustumComponentVisibilityTestFlag::RENDER_COMPONENTS);
 
-	Bool wantsLightComponents = testedFrc.visibilityTestsEnabled(FrustumComponentVisibilityTestFlag::LIGHT_COMPONENTS);
+	const Bool wantsLightComponents =
+		testedFrc.visibilityTestsEnabled(FrustumComponentVisibilityTestFlag::LIGHT_COMPONENTS);
 
-	Bool wantsFlareComponents =
+	const Bool wantsFlareComponents =
 		testedFrc.visibilityTestsEnabled(FrustumComponentVisibilityTestFlag::LENS_FLARE_COMPONENTS);
 
-	Bool wantsShadowCasters = testedFrc.visibilityTestsEnabled(FrustumComponentVisibilityTestFlag::SHADOW_CASTERS);
+	const Bool wantsShadowCasters =
+		testedFrc.visibilityTestsEnabled(FrustumComponentVisibilityTestFlag::SHADOW_CASTERS);
 
-	Bool wantsReflectionProbes =
+	const Bool wantsReflectionProbes =
 		testedFrc.visibilityTestsEnabled(FrustumComponentVisibilityTestFlag::REFLECTION_PROBES);
 
-	Bool wantsReflectionProxies =
+	const Bool wantsReflectionProxies =
 		testedFrc.visibilityTestsEnabled(FrustumComponentVisibilityTestFlag::REFLECTION_PROXIES);
 
-	Bool wantsDecals = testedFrc.visibilityTestsEnabled(FrustumComponentVisibilityTestFlag::DECALS);
+	const Bool wantsDecals = testedFrc.visibilityTestsEnabled(FrustumComponentVisibilityTestFlag::DECALS);
+
+	const Bool wantsEarlyZ =
+		testedFrc.visibilityTestsEnabled(FrustumComponentVisibilityTestFlag::EARLY_Z) && m_visCtx->m_earlyZDist > 0.0f;
 
 	// Chose the test range and a few other things
 	PtrSize start, end;
@@ -368,6 +373,13 @@ void VisibilityTestTask::test(ThreadHive& hive)
 				// Compute distance from the frustum
 				const Plane& nearPlane = testedFrc.getFrustum().getPlanesWorldSpace()[FrustumPlaneType::NEAR];
 				el->m_distanceFromCamera = max(0.0f, sps[0].m_sp->getAabb().testPlane(nearPlane));
+
+				if(wantsEarlyZ && el->m_distanceFromCamera < m_visCtx->m_earlyZDist
+					&& !rc->getMaterial().isForwardShading())
+				{
+					RenderableQueueElement* el2 = m_result.m_earlyZRenderables.newElement(alloc);
+					*el2 = *el;
+				}
 			}
 		}
 
@@ -531,6 +543,7 @@ void CombineResultsTask::combine()
 	}
 
 	ANKI_VIS_COMBINE(RenderableQueueElement, m_renderables);
+	ANKI_VIS_COMBINE(RenderableQueueElement, m_earlyZRenderables);
 	ANKI_VIS_COMBINE(RenderableQueueElement, m_forwardShadingRenderables);
 	ANKI_VIS_COMBINE_AND_PTR(PointLightQueueElement, m_pointLights, m_shadowPointLights);
 	ANKI_VIS_COMBINE_AND_PTR(SpotLightQueueElement, m_spotLights, m_shadowSpotLights);
@@ -554,8 +567,11 @@ void CombineResultsTask::combine()
 #endif
 
 	// Sort some of the arrays
-	std::sort(m_results->m_renderables.getBegin(),
-		m_results->m_renderables.getEnd(),
+	std::sort(
+		m_results->m_renderables.getBegin(), m_results->m_renderables.getEnd(), MaterialDistanceSortFunctor(20.0f));
+
+	std::sort(m_results->m_earlyZRenderables.getBegin(),
+		m_results->m_earlyZRenderables.getEnd(),
 		DistanceSortFunctor<RenderableQueueElement>());
 
 	std::sort(m_results->m_forwardShadingRenderables.getBegin(),
@@ -673,6 +689,7 @@ void doVisibilityTests(SceneNode& fsn, SceneGraph& scene, RenderQueue& rqueue)
 
 	VisibilityContext ctx;
 	ctx.m_scene = &scene;
+	ctx.m_earlyZDist = scene.getEarlyZDistance();
 	ctx.submitNewWork(fsn.getComponent<FrustumComponent>(), rqueue, hive);
 
 	hive.waitAllTasks();

+ 31 - 0
src/anki/scene/VisibilityInternal.h

@@ -43,6 +43,34 @@ public:
 	}
 };
 
+/// Material and distance sort.
+class MaterialDistanceSortFunctor
+{
+public:
+	MaterialDistanceSortFunctor(F32 distanceGranularity)
+		: m_distGranularity(1.0f / distanceGranularity)
+	{
+	}
+
+	Bool operator()(const RenderableQueueElement& a, const RenderableQueueElement& b)
+	{
+		const U aClass = a.m_distanceFromCamera * m_distGranularity;
+		const U bClass = b.m_distanceFromCamera * m_distGranularity;
+
+		if(aClass == bClass && a.m_callback == b.m_callback)
+		{
+			return a.m_mergeKey < b.m_mergeKey;
+		}
+		else
+		{
+			return a.m_distanceFromCamera < b.m_distanceFromCamera;
+		}
+	}
+
+private:
+	F32 m_distGranularity;
+};
+
 /// Storage for a single element type.
 template<typename T, U INITIAL_STORAGE_SIZE = 32, U STORAGE_GROW_RATE = 4>
 class TRenderQueueElementStorage
@@ -76,6 +104,7 @@ class RenderQueueView
 public:
 	TRenderQueueElementStorage<RenderableQueueElement> m_renderables; ///< Deferred shading or shadow renderables.
 	TRenderQueueElementStorage<RenderableQueueElement> m_forwardShadingRenderables;
+	TRenderQueueElementStorage<RenderableQueueElement> m_earlyZRenderables;
 	TRenderQueueElementStorage<PointLightQueueElement> m_pointLights;
 	TRenderQueueElementStorage<U32> m_shadowPointLights;
 	TRenderQueueElementStorage<SpotLightQueueElement> m_spotLights;
@@ -94,6 +123,8 @@ public:
 	SceneGraph* m_scene = nullptr;
 	Atomic<U32> m_testsCount = {0};
 
+	F32 m_earlyZDist = -1.0f; ///< Cache this.
+
 	List<FrustumComponent*> m_testedFrcs;
 	Mutex m_mtx;
 

+ 0 - 8
src/anki/util/DynamicArray.h

@@ -355,14 +355,6 @@ public:
 		*this = std::move(b);
 	}
 
-	~WeakArray()
-	{
-#if ANKI_EXTRA_CHECKS
-		m_data = nullptr;
-		m_size = 0;
-#endif
-	}
-
 	/// Copy.
 	WeakArray& operator=(const WeakArray& b)
 	{

+ 2 - 2
tests/gr/Gr.cpp

@@ -291,8 +291,8 @@ static StagingGpuMemoryManager* stagingMem = nullptr;
 	Config cfg;                                                                \
 	cfg.set("width", WIDTH);                                                   \
 	cfg.set("height", HEIGHT);                                                 \
-	cfg.set("debugContext", true);                                             \
-	cfg.set("vsync", false);                                                   \
+	cfg.set("window.debugContext", true);                                      \
+	cfg.set("window.vsync", false);                                            \
 	win = createWindow(cfg);                                                   \
 	gr = createGrManager(cfg, win);                                            \
 	ANKI_TEST_EXPECT_NO_ERR(stagingMem->init(gr, Config()));                   \

+ 2 - 2
tests/ui/Ui.cpp

@@ -55,8 +55,8 @@ ANKI_TEST(Ui, Ui)
 {
 	Config cfg;
 	initConfig(cfg);
-	cfg.set("vsync", 1);
-	cfg.set("debugContext", 0);
+	cfg.set("window.vsync", 1);
+	cfg.set("window.debugContext", 0);
 
 	NativeWindow* win = createWindow(cfg);
 	Input* in = new Input();