Browse Source

Merge pull request #47059 from BastiaanOlij/forward_mobile_render

Rémi Verschelde 4 years ago
parent
commit
dad77eed5c
28 changed files with 7137 additions and 1486 deletions
  1. 0 4
      doc/classes/ProjectSettings.xml
  2. 1 0
      servers/rendering/renderer_rd/SCsub
  3. 0 11
      servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp
  4. 1 2
      servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h
  5. 10 17
      servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp
  6. 5 0
      servers/rendering/renderer_rd/forward_mobile/SCsub
  7. 2163 0
      servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp
  8. 604 0
      servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h
  9. 833 0
      servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp
  10. 203 0
      servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.h
  11. 10 1
      servers/rendering/renderer_rd/renderer_compositor_rd.cpp
  12. 1 0
      servers/rendering/renderer_rd/renderer_compositor_rd.h
  13. 0 2
      servers/rendering/renderer_rd/renderer_scene_gi_rd.cpp
  14. 145 24
      servers/rendering/renderer_rd/renderer_scene_render_rd.cpp
  15. 9 1
      servers/rendering/renderer_rd/renderer_scene_render_rd.h
  16. 0 102
      servers/rendering/renderer_rd/shaders/cluster_data_inc.glsl
  17. 18 0
      servers/rendering/renderer_rd/shaders/decal_data_inc.glsl
  18. 83 0
      servers/rendering/renderer_rd/shaders/light_data_inc.glsl
  19. 58 0
      servers/rendering/renderer_rd/shaders/scene_forward_aa_inc.glsl
  20. 7 1303
      servers/rendering/renderer_rd/shaders/scene_forward_clustered.glsl
  21. 7 7
      servers/rendering/renderer_rd/shaders/scene_forward_clustered_inc.glsl
  22. 242 0
      servers/rendering/renderer_rd/shaders/scene_forward_gi_inc.glsl
  23. 1023 0
      servers/rendering/renderer_rd/shaders/scene_forward_lights_inc.glsl
  24. 1476 0
      servers/rendering/renderer_rd/shaders/scene_forward_mobile.glsl
  25. 220 0
      servers/rendering/renderer_rd/shaders/scene_forward_mobile_inc.glsl
  26. 1 0
      servers/rendering/renderer_rd/shaders/volumetric_fog.glsl
  27. 11 10
      servers/rendering/renderer_scene_cull.cpp
  28. 6 2
      servers/rendering_server.cpp

+ 0 - 4
doc/classes/ProjectSettings.xml

@@ -1356,10 +1356,6 @@
 			[b]FIXME:[/b] No longer valid after DisplayServer split:
 			In such cases, this property is not updated, so use [code]OS.get_current_video_driver[/code] to query it at run-time.
 		</member>
-		<member name="rendering/driver/rd_renderer/use_low_end_renderer" type="bool" setter="" getter="" default="false">
-		</member>
-		<member name="rendering/driver/rd_renderer/use_low_end_renderer.mobile" type="bool" setter="" getter="" default="true">
-		</member>
 		<member name="rendering/driver/threads/thread_model" type="int" setter="" getter="" default="1">
 			Thread model for rendering. Rendering on a thread can vastly improve performance, but synchronizing to the main thread can cause a bit more jitter.
 		</member>

+ 1 - 0
servers/rendering/renderer_rd/SCsub

@@ -5,4 +5,5 @@ Import("env")
 env.add_source_files(env.servers_sources, "*.cpp")
 
 SConscript("forward_clustered/SCsub")
+SConscript("forward_mobile/SCsub")
 SConscript("shaders/SCsub")

+ 0 - 11
servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp

@@ -284,8 +284,6 @@ void RenderForwardClustered::_allocate_normal_roughness_texture(RenderBufferData
 		fb.push_back(rb->normal_roughness_buffer_msaa);
 		rb->depth_normal_roughness_fb = RD::get_singleton()->framebuffer_create(fb);
 	}
-
-	_render_buffers_clear_uniform_set(rb);
 }
 
 RendererSceneRenderRD::RenderBufferData *RenderForwardClustered::_create_render_buffer_data() {
@@ -2305,15 +2303,6 @@ RID RenderForwardClustered::_setup_sdfgi_render_pass_uniform_set(RID p_albedo_te
 	return sdfgi_pass_uniform_set;
 }
 
-void RenderForwardClustered::_render_buffers_clear_uniform_set(RenderBufferDataForwardClustered *rb) {
-}
-
-void RenderForwardClustered::_render_buffers_uniform_set_changed(RID p_render_buffers) {
-	RenderBufferDataForwardClustered *rb = (RenderBufferDataForwardClustered *)render_buffers_get_data(p_render_buffers);
-
-	_render_buffers_clear_uniform_set(rb);
-}
-
 RID RenderForwardClustered::_render_buffers_get_normal_texture(RID p_render_buffers) {
 	RenderBufferDataForwardClustered *rb = (RenderBufferDataForwardClustered *)render_buffers_get_data(p_render_buffers);
 

+ 1 - 2
servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h

@@ -118,8 +118,6 @@ class RenderForwardClustered : public RendererSceneRenderRD {
 	uint64_t lightmap_texture_array_version = 0xFFFFFFFF;
 
 	virtual void _base_uniforms_changed();
-	void _render_buffers_clear_uniform_set(RenderBufferDataForwardClustered *rb);
-	virtual void _render_buffers_uniform_set_changed(RID p_render_buffers);
 	virtual RID _render_buffers_get_normal_texture(RID p_render_buffers);
 
 	void _update_render_base_uniform_set();
@@ -202,6 +200,7 @@ class RenderForwardClustered : public RendererSceneRenderRD {
 	};
 
 	struct SceneState {
+		// This struct is loaded into Set 1 - Binding 0, populated at start of rendering a frame, must match with shader code
 		struct UBO {
 			float projection_matrix[16];
 			float inv_projection_matrix[16];

+ 10 - 17
servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp

@@ -146,12 +146,17 @@ void SceneShaderForwardClustered::ShaderData::set_code(const String &p_code) {
 	for (int i = 0; i < gen_code.defines.size(); i++) {
 		print_line(gen_code.defines[i]);
 	}
+
+	Map<String, String>::Element * el = gen_code.code.front();
+	while (el) {
+		print_line("\n**code " + el->key() + ":\n" + el->value());
+
+		el = el->next();
+	}
+
 	print_line("\n**uniforms:\n" + gen_code.uniforms);
-	print_line("\n**vertex_globals:\n" + gen_code.vertex_global);
-	print_line("\n**vertex_code:\n" + gen_code.vertex);
-	print_line("\n**fragment_globals:\n" + gen_code.fragment_global);
-	print_line("\n**fragment_code:\n" + gen_code.fragment);
-	print_line("\n**light_code:\n" + gen_code.light);
+	print_line("\n**vertex_globals:\n" + gen_code.stage_globals[ShaderCompilerRD::STAGE_VERTEX]);
+	print_line("\n**fragment_globals:\n" + gen_code.stage_globals[ShaderCompilerRD::STAGE_FRAGMENT]);
 #endif
 	shader_singleton->shader.version_set_code(version, gen_code.code, gen_code.uniforms, gen_code.stage_globals[ShaderCompilerRD::STAGE_VERTEX], gen_code.stage_globals[ShaderCompilerRD::STAGE_FRAGMENT], gen_code.defines);
 	ERR_FAIL_COND(!shader_singleton->shader.version_is_valid(version));
@@ -566,18 +571,6 @@ void SceneShaderForwardClustered::init(RendererStorageRD *p_storage, const Strin
 		shader_versions.push_back("\n#define USE_LIGHTMAP\n");
 		shader_versions.push_back("\n#define MODE_MULTIPLE_RENDER_TARGETS\n#define USE_LIGHTMAP\n");
 		shader.initialize(shader_versions, p_defines);
-
-		/*
-		if (p_is_low_end) {
-			//disable the high end versions
-			shader.set_variant_enabled(SHADER_VERSION_DEPTH_PASS_WITH_NORMAL_AND_ROUGHNESS, false);
-			shader.set_variant_enabled(SHADER_VERSION_DEPTH_PASS_WITH_NORMAL_AND_ROUGHNESS_AND_GIPROBE, false);
-			shader.set_variant_enabled(SHADER_VERSION_DEPTH_PASS_WITH_SDF, false);
-			shader.set_variant_enabled(SHADER_VERSION_COLOR_PASS_WITH_FORWARD_GI, false);
-			shader.set_variant_enabled(SHADER_VERSION_COLOR_PASS_WITH_SEPARATE_SPECULAR, false);
-			shader.set_variant_enabled(SHADER_VERSION_LIGHTMAP_COLOR_PASS_WITH_SEPARATE_SPECULAR, false);
-		}
-		*/
 	}
 
 	storage->shader_set_data_request_function(RendererStorageRD::SHADER_TYPE_3D, _create_shader_funcs);

+ 5 - 0
servers/rendering/renderer_rd/forward_mobile/SCsub

@@ -0,0 +1,5 @@
+#!/usr/bin/env python
+
+Import("env")
+
+env.add_source_files(env.servers_sources, "*.cpp")

+ 2163 - 0
servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp

@@ -0,0 +1,2163 @@
+/*************************************************************************/
+/*  render_forward_mobile.cpp                                            */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md).   */
+/*                                                                       */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the       */
+/* "Software"), to deal in the Software without restriction, including   */
+/* without limitation the rights to use, copy, modify, merge, publish,   */
+/* distribute, sublicense, and/or sell copies of the Software, and to    */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions:                                             */
+/*                                                                       */
+/* The above copyright notice and this permission notice shall be        */
+/* included in all copies or substantial portions of the Software.       */
+/*                                                                       */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
+/*************************************************************************/
+
+#include "render_forward_mobile.h"
+#include "core/config/project_settings.h"
+#include "servers/rendering/rendering_device.h"
+#include "servers/rendering/rendering_server_default.h"
+
+using namespace RendererSceneRenderImplementation;
+
+/* Render buffer */
+
+void RenderForwardMobile::RenderBufferDataForwardMobile::clear() {
+	if (color_msaa.is_valid()) {
+		RD::get_singleton()->free(color_msaa);
+		color_msaa = RID();
+	}
+
+	if (depth_msaa.is_valid()) {
+		RD::get_singleton()->free(depth_msaa);
+		depth_msaa = RID();
+	}
+
+	color = RID();
+	depth = RID();
+	color_fb = RID();
+}
+
+void RenderForwardMobile::RenderBufferDataForwardMobile::configure(RID p_color_buffer, RID p_depth_buffer, int p_width, int p_height, RS::ViewportMSAA p_msaa) {
+	clear();
+
+	msaa = p_msaa;
+
+	width = p_width;
+	height = p_height;
+
+	color = p_color_buffer;
+	depth = p_depth_buffer;
+
+	// re-introduce setting up msaa? For now we ignore this...
+
+	if (p_msaa == RS::VIEWPORT_MSAA_DISABLED) {
+		Vector<RID> fb;
+		fb.push_back(p_color_buffer);
+		fb.push_back(depth);
+
+		color_fb = RD::get_singleton()->framebuffer_create(fb);
+	} else {
+		RD::TextureFormat tf;
+		tf.format = RD::DATA_FORMAT_R16G16B16A16_SFLOAT;
+		tf.width = p_width;
+		tf.height = p_height;
+		tf.texture_type = RD::TEXTURE_TYPE_2D;
+		tf.usage_bits = RD::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT | RD::TEXTURE_USAGE_CAN_COPY_FROM_BIT | RD::TEXTURE_USAGE_SAMPLING_BIT;
+
+		RD::TextureSamples ts[RS::VIEWPORT_MSAA_MAX] = {
+			RD::TEXTURE_SAMPLES_1,
+			RD::TEXTURE_SAMPLES_2,
+			RD::TEXTURE_SAMPLES_4,
+			RD::TEXTURE_SAMPLES_8,
+			RD::TEXTURE_SAMPLES_16
+		};
+
+		texture_samples = ts[p_msaa];
+		tf.samples = texture_samples;
+
+		color_msaa = RD::get_singleton()->texture_create(tf, RD::TextureView());
+
+		tf.format = RD::get_singleton()->texture_is_format_supported_for_usage(RD::DATA_FORMAT_D24_UNORM_S8_UINT, RD::TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) ? RD::DATA_FORMAT_D24_UNORM_S8_UINT : RD::DATA_FORMAT_D32_SFLOAT_S8_UINT;
+		tf.usage_bits = RD::TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | RD::TEXTURE_USAGE_CAN_COPY_FROM_BIT | RD::TEXTURE_USAGE_SAMPLING_BIT;
+
+		depth_msaa = RD::get_singleton()->texture_create(tf, RD::TextureView());
+
+		{
+			Vector<RID> fb;
+			fb.push_back(color_msaa);
+			fb.push_back(depth_msaa);
+
+			color_fb = RD::get_singleton()->framebuffer_create(fb);
+		}
+	}
+}
+
+RenderForwardMobile::RenderBufferDataForwardMobile::~RenderBufferDataForwardMobile() {
+	clear();
+}
+
+RendererSceneRenderRD::RenderBufferData *RenderForwardMobile::_create_render_buffer_data() {
+	return memnew(RenderBufferDataForwardMobile);
+}
+
+bool RenderForwardMobile::free(RID p_rid) {
+	if (RendererSceneRenderRD::free(p_rid)) {
+		return true;
+	}
+	return false;
+}
+
+/* Render functions */
+
+RID RenderForwardMobile::_setup_render_pass_uniform_set(RenderListType p_render_list, RID p_render_buffers, RID p_radiance_texture, RID p_shadow_atlas, RID p_reflection_atlas, const PagedArray<RID> &p_lightmaps, bool p_use_directional_shadow_atlas, int p_index) {
+	//there should always be enough uniform buffers for render passes, otherwise bugs
+	ERR_FAIL_INDEX_V(p_index, (int)scene_state.uniform_buffers.size(), RID());
+
+	RenderBufferDataForwardMobile *rb = nullptr;
+	if (p_render_buffers.is_valid()) {
+		rb = (RenderBufferDataForwardMobile *)render_buffers_get_data(p_render_buffers);
+	}
+
+	// default render buffer and scene state uniform set
+	// loaded into set 1
+
+	Vector<RD::Uniform> uniforms;
+
+	{
+		RD::Uniform u;
+		u.binding = 0;
+		u.uniform_type = RD::UNIFORM_TYPE_UNIFORM_BUFFER;
+		u.ids.push_back(scene_state.uniform_buffers[p_index]);
+		uniforms.push_back(u);
+	}
+
+	{
+		RID radiance_texture;
+		if (p_radiance_texture.is_valid()) {
+			radiance_texture = p_radiance_texture;
+		} else {
+			radiance_texture = storage->texture_rd_get_default(is_using_radiance_cubemap_array() ? RendererStorageRD::DEFAULT_RD_TEXTURE_CUBEMAP_ARRAY_BLACK : RendererStorageRD::DEFAULT_RD_TEXTURE_CUBEMAP_BLACK);
+		}
+		RD::Uniform u;
+		u.binding = 2;
+		u.uniform_type = RD::UNIFORM_TYPE_TEXTURE;
+		u.ids.push_back(radiance_texture);
+		uniforms.push_back(u);
+	}
+
+	{
+		RID ref_texture = p_reflection_atlas.is_valid() ? reflection_atlas_get_texture(p_reflection_atlas) : RID();
+		RD::Uniform u;
+		u.binding = 3;
+		u.uniform_type = RD::UNIFORM_TYPE_TEXTURE;
+		if (ref_texture.is_valid()) {
+			u.ids.push_back(ref_texture);
+		} else {
+			u.ids.push_back(storage->texture_rd_get_default(RendererStorageRD::DEFAULT_RD_TEXTURE_CUBEMAP_ARRAY_BLACK));
+		}
+		uniforms.push_back(u);
+	}
+
+	{
+		RD::Uniform u;
+		u.binding = 4;
+		u.uniform_type = RD::UNIFORM_TYPE_TEXTURE;
+		RID texture;
+		if (p_shadow_atlas.is_valid()) {
+			texture = shadow_atlas_get_texture(p_shadow_atlas);
+		}
+		if (!texture.is_valid()) {
+			texture = storage->texture_rd_get_default(RendererStorageRD::DEFAULT_RD_TEXTURE_WHITE);
+		}
+		u.ids.push_back(texture);
+		uniforms.push_back(u);
+	}
+	{
+		RD::Uniform u;
+		u.binding = 5;
+		u.uniform_type = RD::UNIFORM_TYPE_TEXTURE;
+		if (p_use_directional_shadow_atlas && directional_shadow_get_texture().is_valid()) {
+			u.ids.push_back(directional_shadow_get_texture());
+		} else {
+			u.ids.push_back(storage->texture_rd_get_default(RendererStorageRD::DEFAULT_RD_TEXTURE_WHITE));
+		}
+		uniforms.push_back(u);
+	}
+
+	/* we have limited ability to keep textures like this so we're moving this to a set we change before drawing geometry and just pushing the needed texture in */
+	{
+		RD::Uniform u;
+		u.binding = 6;
+		u.uniform_type = RD::UNIFORM_TYPE_TEXTURE;
+		u.ids.resize(scene_state.max_lightmaps);
+		RID default_tex = storage->texture_rd_get_default(RendererStorageRD::DEFAULT_RD_TEXTURE_2D_ARRAY_WHITE);
+		for (uint32_t i = 0; i < scene_state.max_lightmaps; i++) {
+			if (i < p_lightmaps.size()) {
+				RID base = lightmap_instance_get_lightmap(p_lightmaps[i]);
+				RID texture = storage->lightmap_get_texture(base);
+				RID rd_texture = storage->texture_get_rd_texture(texture);
+				u.ids.write[i] = rd_texture;
+			} else {
+				u.ids.write[i] = default_tex;
+			}
+		}
+
+		uniforms.push_back(u);
+	}
+
+	/*
+	{
+		RD::Uniform u;
+		u.binding = 7;
+		u.uniform_type = RD::UNIFORM_TYPE_TEXTURE;
+		u.ids.resize(MAX_GI_PROBES);
+		RID default_tex = storage->texture_rd_get_default(RendererStorageRD::DEFAULT_RD_TEXTURE_3D_WHITE);
+		for (int i = 0; i < MAX_GI_PROBES; i++) {
+			if (i < (int)p_gi_probes.size()) {
+				RID tex = gi.gi_probe_instance_get_texture(p_gi_probes[i]);
+				if (!tex.is_valid()) {
+					tex = default_tex;
+				}
+				u.ids.write[i] = tex;
+			} else {
+				u.ids.write[i] = default_tex;
+			}
+		}
+
+		uniforms.push_back(u);
+	}
+
+	{
+		RD::Uniform u;
+		u.binding = 8;
+		u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER;
+		RID cb = p_cluster_buffer.is_valid() ? p_cluster_buffer : default_vec4_xform_buffer;
+		u.ids.push_back(cb);
+		uniforms.push_back(u);
+	}
+	*/
+
+	{
+		RD::Uniform u;
+		u.binding = 9;
+		u.uniform_type = RD::UNIFORM_TYPE_TEXTURE;
+		RID texture = (false && rb && rb->depth.is_valid()) ? rb->depth : storage->texture_rd_get_default(RendererStorageRD::DEFAULT_RD_TEXTURE_WHITE);
+		u.ids.push_back(texture);
+		uniforms.push_back(u);
+	}
+	{
+		RD::Uniform u;
+		u.binding = 10;
+		u.uniform_type = RD::UNIFORM_TYPE_TEXTURE;
+		RID bbt = rb ? render_buffers_get_back_buffer_texture(p_render_buffers) : RID();
+		RID texture = bbt.is_valid() ? bbt : storage->texture_rd_get_default(RendererStorageRD::DEFAULT_RD_TEXTURE_BLACK);
+		u.ids.push_back(texture);
+		uniforms.push_back(u);
+	}
+
+	if (p_index >= (int)render_pass_uniform_sets.size()) {
+		render_pass_uniform_sets.resize(p_index + 1);
+	}
+
+	if (render_pass_uniform_sets[p_index].is_valid() && RD::get_singleton()->uniform_set_is_valid(render_pass_uniform_sets[p_index])) {
+		RD::get_singleton()->free(render_pass_uniform_sets[p_index]);
+	}
+
+	render_pass_uniform_sets[p_index] = RD::get_singleton()->uniform_set_create(uniforms, scene_shader.default_shader_rd, RENDER_PASS_UNIFORM_SET);
+	return render_pass_uniform_sets[p_index];
+}
+
+void RenderForwardMobile::_setup_lightmaps(const PagedArray<RID> &p_lightmaps, const Transform &p_cam_transform) {
+	// This probably needs to change...
+	scene_state.lightmaps_used = 0;
+	for (int i = 0; i < (int)p_lightmaps.size(); i++) {
+		if (i >= (int)scene_state.max_lightmaps) {
+			break;
+		}
+
+		RID lightmap = lightmap_instance_get_lightmap(p_lightmaps[i]);
+
+		Basis to_lm = lightmap_instance_get_transform(p_lightmaps[i]).basis.inverse() * p_cam_transform.basis;
+		to_lm = to_lm.inverse().transposed(); //will transform normals
+		RendererStorageRD::store_transform_3x3(to_lm, scene_state.lightmaps[i].normal_xform);
+		scene_state.lightmap_ids[i] = p_lightmaps[i];
+		scene_state.lightmap_has_sh[i] = storage->lightmap_uses_spherical_harmonics(lightmap);
+
+		scene_state.lightmaps_used++;
+	}
+	if (scene_state.lightmaps_used > 0) {
+		RD::get_singleton()->buffer_update(scene_state.lightmap_buffer, 0, sizeof(LightmapData) * scene_state.lightmaps_used, scene_state.lightmaps, RD::BARRIER_MASK_RASTER);
+	}
+}
+
+void RenderForwardMobile::_render_scene(RID p_render_buffer, const Transform &p_cam_transform, const CameraMatrix &p_cam_projection, bool p_cam_ortogonal, const PagedArray<GeometryInstance *> &p_instances, const PagedArray<RID> &p_gi_probes, const PagedArray<RID> &p_lightmaps, RID p_environment, RID p_cluster_buffer, uint32_t p_cluster_size, uint32_t p_cluster_max_elements, RID p_camera_effects, RID p_shadow_atlas, RID p_reflection_atlas, RID p_reflection_probe, int p_reflection_probe_pass, const Color &p_default_bg_color, float p_screen_lod_threshold) {
+	// These are UNUSED here and will not have data parsed from RendererSceneRenderRD:
+	// - p_gi_probes
+	// - p_cluster_buffer
+	// - p_cluster_size
+	// - p_cluster_max_elements
+
+	RenderBufferDataForwardMobile *render_buffer = nullptr;
+	if (p_render_buffer.is_valid()) {
+		render_buffer = (RenderBufferDataForwardMobile *)render_buffers_get_data(p_render_buffer);
+	}
+	RendererSceneEnvironmentRD *env = get_environment(p_environment);
+
+	RENDER_TIMESTAMP("Setup 3D Scene");
+
+	float lod_distance_multiplier = p_cam_projection.get_lod_multiplier();
+	Plane lod_camera_plane(p_cam_transform.get_origin(), -p_cam_transform.basis.get_axis(Vector3::AXIS_Z));
+
+	if (get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_DISABLE_LOD) {
+		p_screen_lod_threshold = 0.0;
+	}
+
+	Vector2 vp_he = p_cam_projection.get_viewport_half_extents();
+	scene_state.ubo.viewport_size[0] = vp_he.x;
+	scene_state.ubo.viewport_size[1] = vp_he.y;
+	scene_state.ubo.directional_light_count = 0;
+
+	Size2i screen_size;
+	RID opaque_framebuffer;
+	RID alpha_framebuffer;
+	bool reverse_cull = false;
+
+	// I don't think we support either of these in our mobile renderer so probably should phase them out
+	bool using_ssr = false;
+	bool using_sss = false;
+
+	if (render_buffer) {
+		// setup rendering to render buffer
+		screen_size.x = render_buffer->width;
+		screen_size.y = render_buffer->height;
+
+		opaque_framebuffer = render_buffer->color_fb;
+		alpha_framebuffer = opaque_framebuffer;
+	} else if (p_reflection_probe.is_valid()) {
+		uint32_t resolution = reflection_probe_instance_get_resolution(p_reflection_probe);
+		screen_size.x = resolution;
+		screen_size.y = resolution;
+
+		opaque_framebuffer = reflection_probe_instance_get_framebuffer(p_reflection_probe, p_reflection_probe_pass);
+		alpha_framebuffer = opaque_framebuffer;
+
+		if (storage->reflection_probe_is_interior(reflection_probe_instance_get_probe(p_reflection_probe))) {
+			p_environment = RID(); //no environment on interiors
+		}
+
+		reverse_cull = true;
+	} else {
+		ERR_FAIL(); //bug?
+	}
+
+	RD::get_singleton()->draw_command_begin_label("Render Setup");
+
+	_setup_lightmaps(p_lightmaps, p_cam_transform);
+	_setup_environment(p_environment, p_render_buffer, p_cam_projection, p_cam_transform, p_reflection_probe, p_reflection_probe.is_valid(), screen_size, p_shadow_atlas, !p_reflection_probe.is_valid(), p_default_bg_color, p_cam_projection.get_z_near(), p_cam_projection.get_z_far(), false);
+
+	_update_render_base_uniform_set(); //may have changed due to the above (light buffer enlarged, as an example)
+
+	_fill_render_list(RENDER_LIST_OPAQUE, p_instances, PASS_MODE_COLOR, p_cam_projection, p_cam_transform, lod_camera_plane, lod_distance_multiplier, p_screen_lod_threshold);
+	render_list[RENDER_LIST_OPAQUE].sort_by_key();
+	render_list[RENDER_LIST_ALPHA].sort_by_depth();
+
+	// we no longer use this...
+	_fill_instance_data(RENDER_LIST_OPAQUE);
+	_fill_instance_data(RENDER_LIST_ALPHA);
+
+	RD::get_singleton()->draw_command_end_label();
+
+	// note, no depth prepass here!
+
+	// setup environment
+	RID radiance_texture;
+	bool draw_sky = false;
+	bool draw_sky_fog_only = false;
+
+	Color clear_color = p_default_bg_color;
+	bool keep_color = false;
+
+	if (get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_OVERDRAW) {
+		clear_color = Color(0, 0, 0, 1); //in overdraw mode, BG should always be black
+	} else if (is_environment(p_environment)) {
+		RS::EnvironmentBG bg_mode = environment_get_background(p_environment);
+		float bg_energy = environment_get_bg_energy(p_environment);
+		switch (bg_mode) {
+			case RS::ENV_BG_CLEAR_COLOR: {
+				clear_color = p_default_bg_color;
+				clear_color.r *= bg_energy;
+				clear_color.g *= bg_energy;
+				clear_color.b *= bg_energy;
+				/*
+				if (render_buffers_has_volumetric_fog(p_render_buffer) || environment_is_fog_enabled(p_environment)) {
+					draw_sky_fog_only = true;
+					storage->material_set_param(sky.sky_scene_state.fog_material, "clear_color", Variant(clear_color.to_linear()));
+				}
+				*/
+			} break;
+			case RS::ENV_BG_COLOR: {
+				clear_color = environment_get_bg_color(p_environment);
+				clear_color.r *= bg_energy;
+				clear_color.g *= bg_energy;
+				clear_color.b *= bg_energy;
+				/*
+				if (render_buffers_has_volumetric_fog(p_render_buffer) || environment_is_fog_enabled(p_environment)) {
+					draw_sky_fog_only = true;
+					storage->material_set_param(sky.sky_scene_state.fog_material, "clear_color", Variant(clear_color.to_linear()));
+				}
+				*/
+			} break;
+			case RS::ENV_BG_SKY: {
+				draw_sky = true;
+			} break;
+			case RS::ENV_BG_CANVAS: {
+				keep_color = true;
+			} break;
+			case RS::ENV_BG_KEEP: {
+				keep_color = true;
+			} break;
+			case RS::ENV_BG_CAMERA_FEED: {
+			} break;
+			default: {
+			}
+		}
+		// setup sky if used for ambient, reflections, or background
+		if (draw_sky || draw_sky_fog_only || environment_get_reflection_source(p_environment) == RS::ENV_REFLECTION_SOURCE_SKY || environment_get_ambient_source(p_environment) == RS::ENV_AMBIENT_SOURCE_SKY) {
+			RENDER_TIMESTAMP("Setup Sky");
+			RD::get_singleton()->draw_command_begin_label("Setup Sky");
+			CameraMatrix projection = p_cam_projection;
+			if (p_reflection_probe.is_valid()) {
+				CameraMatrix correction;
+				correction.set_depth_correction(true);
+				projection = correction * p_cam_projection;
+			}
+
+			sky.setup(env, p_render_buffer, projection, p_cam_transform, screen_size, this);
+
+			RID sky_rid = env->sky;
+			if (sky_rid.is_valid()) {
+				sky.update(env, projection, p_cam_transform, time);
+				radiance_texture = sky.sky_get_radiance_texture_rd(sky_rid);
+			} else {
+				// do not try to draw sky if invalid
+				draw_sky = false;
+			}
+			RD::get_singleton()->draw_command_end_label();
+		}
+	} else {
+		clear_color = p_default_bg_color;
+	}
+
+	// opaque pass
+
+	// !BAS! Look into this, seems most of the code in here related to clustered only, may want to move this code into ForwardClustered/RenderForwardMobile before calling it from here
+	// does trigger shadow map rendering so kinda important
+	_pre_opaque_render(false, false, RID(), RID());
+
+	RD::get_singleton()->draw_command_begin_label("Render Opaque Pass");
+
+	scene_state.ubo.directional_light_count = _get_render_state_directional_light_count();
+
+	_setup_environment(p_environment, p_render_buffer, p_cam_projection, p_cam_transform, p_reflection_probe, p_reflection_probe.is_valid(), screen_size, p_shadow_atlas, !p_reflection_probe.is_valid(), p_default_bg_color, p_cam_projection.get_z_near(), p_cam_projection.get_z_far(), p_render_buffer.is_valid());
+
+	RENDER_TIMESTAMP("Render Opaque Pass");
+
+	RID rp_uniform_set = _setup_render_pass_uniform_set(RENDER_LIST_OPAQUE, p_render_buffer, radiance_texture, p_shadow_atlas, p_reflection_atlas, p_lightmaps, true);
+
+	bool can_continue_color = !scene_state.used_screen_texture && !using_ssr && !using_sss;
+	bool can_continue_depth = !scene_state.used_depth_texture && !using_ssr && !using_sss;
+
+	{
+		bool will_continue_color = (can_continue_color || draw_sky || draw_sky_fog_only);
+		bool will_continue_depth = (can_continue_depth || draw_sky || draw_sky_fog_only);
+
+		// regular forward for now
+		Vector<Color> c;
+		c.push_back(clear_color.to_linear());
+
+		RenderListParameters render_list_params(render_list[RENDER_LIST_OPAQUE].elements.ptr(), render_list[RENDER_LIST_OPAQUE].element_info.ptr(), render_list[RENDER_LIST_OPAQUE].elements.size(), reverse_cull, PASS_MODE_COLOR, rp_uniform_set, get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_WIREFRAME, Vector2(), lod_camera_plane, lod_distance_multiplier, p_screen_lod_threshold);
+		_render_list_with_threads(&render_list_params, opaque_framebuffer, keep_color ? RD::INITIAL_ACTION_KEEP : RD::INITIAL_ACTION_CLEAR, will_continue_color ? RD::FINAL_ACTION_CONTINUE : RD::FINAL_ACTION_READ, RD::INITIAL_ACTION_CLEAR, will_continue_depth ? RD::FINAL_ACTION_CONTINUE : RD::FINAL_ACTION_READ, c, 1.0, 0);
+	}
+
+	RD::get_singleton()->draw_command_end_label();
+
+	if (draw_sky || draw_sky_fog_only) {
+		RENDER_TIMESTAMP("Render Sky");
+
+		CameraMatrix projection = p_cam_projection;
+		if (p_reflection_probe.is_valid()) {
+			CameraMatrix correction;
+			correction.set_depth_correction(true);
+			projection = correction * p_cam_projection;
+		}
+		RD::get_singleton()->draw_command_begin_label("Draw Sky");
+		sky.draw(env, can_continue_color, can_continue_depth, opaque_framebuffer, projection, p_cam_transform, time);
+		RD::get_singleton()->draw_command_end_label();
+	}
+
+	if (render_buffer && !can_continue_color && render_buffer->msaa != RS::VIEWPORT_MSAA_DISABLED) {
+		RD::get_singleton()->texture_resolve_multisample(render_buffer->color_msaa, render_buffer->color);
+		/*
+		if (using_separate_specular) {
+			RD::get_singleton()->texture_resolve_multisample(render_buffer->specular_msaa, render_buffer->specular);
+		}
+		*/
+	}
+
+	if (render_buffer && !can_continue_depth && render_buffer->msaa != RS::VIEWPORT_MSAA_DISABLED) {
+		RD::get_singleton()->texture_resolve_multisample(render_buffer->depth_msaa, render_buffer->depth);
+	}
+
+	// transparent pass
+	RENDER_TIMESTAMP("Render Transparent Pass");
+
+	RD::get_singleton()->draw_command_begin_label("Render Transparent Pass");
+
+	rp_uniform_set = _setup_render_pass_uniform_set(RENDER_LIST_ALPHA, p_render_buffer, radiance_texture, p_shadow_atlas, p_reflection_atlas, p_lightmaps, true);
+
+	_setup_environment(p_environment, p_render_buffer, p_cam_projection, p_cam_transform, p_reflection_probe, p_reflection_probe.is_valid(), screen_size, p_shadow_atlas, !p_reflection_probe.is_valid(), p_default_bg_color, p_cam_projection.get_z_near(), p_cam_projection.get_z_far(), false);
+
+	{
+		RenderListParameters render_list_params(render_list[RENDER_LIST_ALPHA].elements.ptr(), render_list[RENDER_LIST_ALPHA].element_info.ptr(), render_list[RENDER_LIST_ALPHA].elements.size(), reverse_cull, PASS_MODE_COLOR, rp_uniform_set, get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_WIREFRAME, Vector2(), lod_camera_plane, lod_distance_multiplier, p_screen_lod_threshold);
+		_render_list_with_threads(&render_list_params, alpha_framebuffer, can_continue_color ? RD::INITIAL_ACTION_CONTINUE : RD::INITIAL_ACTION_KEEP, RD::FINAL_ACTION_READ, can_continue_depth ? RD::INITIAL_ACTION_CONTINUE : RD::INITIAL_ACTION_KEEP, RD::FINAL_ACTION_READ);
+	}
+
+	RD::get_singleton()->draw_command_end_label();
+
+	RD::get_singleton()->draw_command_begin_label("Resolve");
+
+	if (render_buffer && render_buffer->msaa != RS::VIEWPORT_MSAA_DISABLED) {
+		RD::get_singleton()->texture_resolve_multisample(render_buffer->color_msaa, render_buffer->color);
+	}
+
+	RD::get_singleton()->draw_command_end_label();
+}
+
+/* these are being called from RendererSceneRenderRD::_pre_opaque_render */
+
+void RenderForwardMobile::_render_shadow_begin() {
+	scene_state.shadow_passes.clear();
+	RD::get_singleton()->draw_command_begin_label("Shadow Setup");
+	_update_render_base_uniform_set();
+
+	render_list[RENDER_LIST_SECONDARY].clear();
+}
+
+void RenderForwardMobile::_render_shadow_append(RID p_framebuffer, const PagedArray<GeometryInstance *> &p_instances, const CameraMatrix &p_projection, const Transform &p_transform, float p_zfar, float p_bias, float p_normal_bias, bool p_use_dp, bool p_use_dp_flip, bool p_use_pancake, const Plane &p_camera_plane, float p_lod_distance_multiplier, float p_screen_lod_threshold, const Rect2i &p_rect, bool p_flip_y, bool p_clear_region, bool p_begin, bool p_end) {
+	uint32_t shadow_pass_index = scene_state.shadow_passes.size();
+
+	SceneState::ShadowPass shadow_pass;
+
+	scene_state.ubo.dual_paraboloid_side = p_use_dp_flip ? -1 : 1;
+
+	_setup_environment(RID(), RID(), p_projection, p_transform, RID(), true, Vector2(1, 1), RID(), !p_flip_y, Color(), 0, p_zfar, false, p_use_pancake, shadow_pass_index);
+
+	if (get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_DISABLE_LOD) {
+		p_screen_lod_threshold = 0.0;
+	}
+
+	PassMode pass_mode = p_use_dp ? PASS_MODE_SHADOW_DP : PASS_MODE_SHADOW;
+
+	uint32_t render_list_from = render_list[RENDER_LIST_SECONDARY].elements.size();
+	_fill_render_list(RENDER_LIST_SECONDARY, p_instances, pass_mode, p_projection, p_transform, p_camera_plane, p_lod_distance_multiplier, p_screen_lod_threshold, true);
+	uint32_t render_list_size = render_list[RENDER_LIST_SECONDARY].elements.size() - render_list_from;
+	render_list[RENDER_LIST_SECONDARY].sort_by_key_range(render_list_from, render_list_size);
+	_fill_instance_data(RENDER_LIST_SECONDARY, render_list_from, render_list_size, false);
+
+	{
+		//regular forward for now
+		bool flip_cull = p_use_dp_flip;
+		if (p_flip_y) {
+			flip_cull = !flip_cull;
+		}
+
+		shadow_pass.element_from = render_list_from;
+		shadow_pass.element_count = render_list_size;
+		shadow_pass.flip_cull = flip_cull;
+		shadow_pass.pass_mode = pass_mode;
+
+		shadow_pass.rp_uniform_set = RID(); //will be filled later when instance buffer is complete
+		shadow_pass.camera_plane = p_camera_plane;
+		shadow_pass.screen_lod_threshold = p_screen_lod_threshold;
+		shadow_pass.lod_distance_multiplier = p_lod_distance_multiplier;
+
+		shadow_pass.framebuffer = p_framebuffer;
+		shadow_pass.initial_depth_action = p_begin ? (p_clear_region ? RD::INITIAL_ACTION_CLEAR_REGION : RD::INITIAL_ACTION_CLEAR) : (p_clear_region ? RD::INITIAL_ACTION_CLEAR_REGION_CONTINUE : RD::INITIAL_ACTION_CONTINUE);
+		shadow_pass.final_depth_action = p_end ? RD::FINAL_ACTION_READ : RD::FINAL_ACTION_CONTINUE;
+		shadow_pass.rect = p_rect;
+
+		scene_state.shadow_passes.push_back(shadow_pass);
+	}
+}
+
+void RenderForwardMobile::_render_shadow_process() {
+	//render shadows one after the other, so this can be done un-barriered and the driver can optimize (as well as allow us to run compute at the same time)
+
+	for (uint32_t i = 0; i < scene_state.shadow_passes.size(); i++) {
+		//render passes need to be configured after instance buffer is done, since they need the latest version
+		SceneState::ShadowPass &shadow_pass = scene_state.shadow_passes[i];
+		shadow_pass.rp_uniform_set = _setup_render_pass_uniform_set(RENDER_LIST_SECONDARY, RID(), RID(), RID(), RID(), PagedArray<RID>(), false, i);
+	}
+
+	RD::get_singleton()->draw_command_end_label();
+}
+
+void RenderForwardMobile::_render_shadow_end(uint32_t p_barrier) {
+	RD::get_singleton()->draw_command_begin_label("Shadow Render");
+
+	for (uint32_t i = 0; i < scene_state.shadow_passes.size(); i++) {
+		SceneState::ShadowPass &shadow_pass = scene_state.shadow_passes[i];
+		RenderListParameters render_list_parameters(render_list[RENDER_LIST_SECONDARY].elements.ptr() + shadow_pass.element_from, render_list[RENDER_LIST_SECONDARY].element_info.ptr() + shadow_pass.element_from, shadow_pass.element_count, shadow_pass.flip_cull, shadow_pass.pass_mode, shadow_pass.rp_uniform_set, false, Vector2(), shadow_pass.camera_plane, shadow_pass.lod_distance_multiplier, shadow_pass.screen_lod_threshold, shadow_pass.element_from, RD::BARRIER_MASK_NO_BARRIER);
+		_render_list_with_threads(&render_list_parameters, shadow_pass.framebuffer, RD::INITIAL_ACTION_DROP, RD::FINAL_ACTION_DISCARD, shadow_pass.initial_depth_action, shadow_pass.final_depth_action, Vector<Color>(), 1.0, 0, shadow_pass.rect);
+	}
+
+	if (p_barrier != RD::BARRIER_MASK_NO_BARRIER) {
+		RD::get_singleton()->barrier(RD::BARRIER_MASK_RASTER, p_barrier);
+	}
+	RD::get_singleton()->draw_command_end_label();
+}
+
+/* */
+
+void RenderForwardMobile::_render_material(const Transform &p_cam_transform, const CameraMatrix &p_cam_projection, bool p_cam_ortogonal, const PagedArray<GeometryInstance *> &p_instances, RID p_framebuffer, const Rect2i &p_region) {
+	RENDER_TIMESTAMP("Setup Rendering Material");
+
+	RD::get_singleton()->draw_command_begin_label("Render Material");
+
+	_update_render_base_uniform_set();
+
+	scene_state.ubo.dual_paraboloid_side = 0;
+	scene_state.ubo.material_uv2_mode = false;
+
+	_setup_environment(RID(), RID(), p_cam_projection, p_cam_transform, RID(), true, Vector2(1, 1), RID(), false, Color(), 0, 0);
+
+	PassMode pass_mode = PASS_MODE_DEPTH_MATERIAL;
+	_fill_render_list(RENDER_LIST_SECONDARY, p_instances, pass_mode, p_cam_projection, p_cam_transform);
+	render_list[RENDER_LIST_SECONDARY].sort_by_key();
+	_fill_instance_data(RENDER_LIST_SECONDARY);
+
+	RID rp_uniform_set = _setup_render_pass_uniform_set(RENDER_LIST_SECONDARY, RID(), RID(), RID(), RID(), PagedArray<RID>());
+
+	RENDER_TIMESTAMP("Render Material");
+
+	{
+		RenderListParameters render_list_params(render_list[RENDER_LIST_SECONDARY].elements.ptr(), render_list[RENDER_LIST_SECONDARY].element_info.ptr(), render_list[RENDER_LIST_SECONDARY].elements.size(), true, pass_mode, rp_uniform_set);
+		//regular forward for now
+		Vector<Color> clear;
+		clear.push_back(Color(0, 0, 0, 0));
+		clear.push_back(Color(0, 0, 0, 0));
+		clear.push_back(Color(0, 0, 0, 0));
+		clear.push_back(Color(0, 0, 0, 0));
+		clear.push_back(Color(0, 0, 0, 0));
+		RD::DrawListID draw_list = RD::get_singleton()->draw_list_begin(p_framebuffer, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_READ, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_READ, clear, 1.0, 0, p_region);
+		_render_list(draw_list, RD::get_singleton()->framebuffer_get_format(p_framebuffer), &render_list_params, 0, render_list_params.element_count);
+		RD::get_singleton()->draw_list_end();
+	}
+
+	RD::get_singleton()->draw_command_end_label();
+}
+
+void RenderForwardMobile::_render_uv2(const PagedArray<GeometryInstance *> &p_instances, RID p_framebuffer, const Rect2i &p_region) {
+	RENDER_TIMESTAMP("Setup Rendering UV2");
+
+	RD::get_singleton()->draw_command_begin_label("Render UV2");
+
+	_update_render_base_uniform_set();
+
+	scene_state.ubo.dual_paraboloid_side = 0;
+	scene_state.ubo.material_uv2_mode = true;
+
+	_setup_environment(RID(), RID(), CameraMatrix(), Transform(), RID(), true, Vector2(1, 1), RID(), false, Color(), 0, 0);
+
+	PassMode pass_mode = PASS_MODE_DEPTH_MATERIAL;
+	_fill_render_list(RENDER_LIST_SECONDARY, p_instances, pass_mode, CameraMatrix(), Transform());
+	render_list[RENDER_LIST_SECONDARY].sort_by_key();
+	_fill_instance_data(RENDER_LIST_SECONDARY);
+
+	RID rp_uniform_set = _setup_render_pass_uniform_set(RENDER_LIST_SECONDARY, RID(), RID(), RID(), RID(), PagedArray<RID>());
+
+	RENDER_TIMESTAMP("Render Material");
+
+	{
+		RenderListParameters render_list_params(render_list[RENDER_LIST_SECONDARY].elements.ptr(), render_list[RENDER_LIST_SECONDARY].element_info.ptr(), render_list[RENDER_LIST_SECONDARY].elements.size(), true, pass_mode, rp_uniform_set, true);
+		//regular forward for now
+		Vector<Color> clear;
+		clear.push_back(Color(0, 0, 0, 0));
+		clear.push_back(Color(0, 0, 0, 0));
+		clear.push_back(Color(0, 0, 0, 0));
+		clear.push_back(Color(0, 0, 0, 0));
+		clear.push_back(Color(0, 0, 0, 0));
+		RD::DrawListID draw_list = RD::get_singleton()->draw_list_begin(p_framebuffer, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_READ, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_READ, clear, 1.0, 0, p_region);
+
+		const int uv_offset_count = 9;
+		static const Vector2 uv_offsets[uv_offset_count] = {
+			Vector2(-1, 1),
+			Vector2(1, 1),
+			Vector2(1, -1),
+			Vector2(-1, -1),
+			Vector2(-1, 0),
+			Vector2(1, 0),
+			Vector2(0, -1),
+			Vector2(0, 1),
+			Vector2(0, 0),
+
+		};
+
+		for (int i = 0; i < uv_offset_count; i++) {
+			Vector2 ofs = uv_offsets[i];
+			ofs.x /= p_region.size.width;
+			ofs.y /= p_region.size.height;
+			render_list_params.uv_offset = ofs;
+			_render_list(draw_list, RD::get_singleton()->framebuffer_get_format(p_framebuffer), &render_list_params, 0, render_list_params.element_count); //first wireframe, for pseudo conservative
+		}
+		render_list_params.uv_offset = Vector2();
+		_render_list(draw_list, RD::get_singleton()->framebuffer_get_format(p_framebuffer), &render_list_params, 0, render_list_params.element_count); //second regular triangles
+
+		RD::get_singleton()->draw_list_end();
+	}
+
+	RD::get_singleton()->draw_command_end_label();
+}
+
+void RenderForwardMobile::_render_sdfgi(RID p_render_buffers, const Vector3i &p_from, const Vector3i &p_size, const AABB &p_bounds, const PagedArray<GeometryInstance *> &p_instances, const RID &p_albedo_texture, const RID &p_emission_texture, const RID &p_emission_aniso_texture, const RID &p_geom_facing_texture) {
+	// we don't do GI in low end..
+}
+
+void RenderForwardMobile::_render_particle_collider_heightfield(RID p_fb, const Transform &p_cam_transform, const CameraMatrix &p_cam_projection, const PagedArray<GeometryInstance *> &p_instances) {
+	RENDER_TIMESTAMP("Setup Render Collider Heightfield");
+
+	RD::get_singleton()->draw_command_begin_label("Render Collider Heightfield");
+
+	_update_render_base_uniform_set();
+	scene_state.ubo.dual_paraboloid_side = 0;
+
+	_setup_environment(RID(), RID(), p_cam_projection, p_cam_transform, RID(), true, Vector2(1, 1), RID(), true, Color(), 0, p_cam_projection.get_z_far(), false, false);
+
+	PassMode pass_mode = PASS_MODE_SHADOW;
+
+	_fill_render_list(RENDER_LIST_SECONDARY, p_instances, pass_mode, p_cam_projection, p_cam_transform);
+	render_list[RENDER_LIST_SECONDARY].sort_by_key();
+	_fill_instance_data(RENDER_LIST_SECONDARY);
+
+	RID rp_uniform_set = _setup_render_pass_uniform_set(RENDER_LIST_SECONDARY, RID(), RID(), RID(), RID(), PagedArray<RID>());
+
+	RENDER_TIMESTAMP("Render Collider Heightfield");
+
+	{
+		//regular forward for now
+		RenderListParameters render_list_params(render_list[RENDER_LIST_SECONDARY].elements.ptr(), render_list[RENDER_LIST_SECONDARY].element_info.ptr(), render_list[RENDER_LIST_SECONDARY].elements.size(), false, pass_mode, rp_uniform_set);
+		_render_list_with_threads(&render_list_params, p_fb, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_READ, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_READ);
+	}
+	RD::get_singleton()->draw_command_end_label();
+}
+
+void RenderForwardMobile::_base_uniforms_changed() {
+	if (!render_base_uniform_set.is_null() && RD::get_singleton()->uniform_set_is_valid(render_base_uniform_set)) {
+		RD::get_singleton()->free(render_base_uniform_set);
+	}
+	render_base_uniform_set = RID();
+}
+
+void RenderForwardMobile::_update_render_base_uniform_set() {
+	if (render_base_uniform_set.is_null() || !RD::get_singleton()->uniform_set_is_valid(render_base_uniform_set) || (lightmap_texture_array_version != storage->lightmap_array_get_version())) {
+		if (render_base_uniform_set.is_valid() && RD::get_singleton()->uniform_set_is_valid(render_base_uniform_set)) {
+			RD::get_singleton()->free(render_base_uniform_set);
+		}
+
+		// This is all loaded into set 0
+
+		lightmap_texture_array_version = storage->lightmap_array_get_version();
+
+		Vector<RD::Uniform> uniforms;
+
+		{
+			RD::Uniform u;
+			u.uniform_type = RD::UNIFORM_TYPE_SAMPLER;
+			u.binding = 1;
+			u.ids.resize(12);
+			RID *ids_ptr = u.ids.ptrw();
+			ids_ptr[0] = storage->sampler_rd_get_default(RS::CANVAS_ITEM_TEXTURE_FILTER_NEAREST, RS::CANVAS_ITEM_TEXTURE_REPEAT_DISABLED);
+			ids_ptr[1] = storage->sampler_rd_get_default(RS::CANVAS_ITEM_TEXTURE_FILTER_LINEAR, RS::CANVAS_ITEM_TEXTURE_REPEAT_DISABLED);
+			ids_ptr[2] = storage->sampler_rd_get_default(RS::CANVAS_ITEM_TEXTURE_FILTER_NEAREST_WITH_MIPMAPS, RS::CANVAS_ITEM_TEXTURE_REPEAT_DISABLED);
+			ids_ptr[3] = storage->sampler_rd_get_default(RS::CANVAS_ITEM_TEXTURE_FILTER_LINEAR_WITH_MIPMAPS, RS::CANVAS_ITEM_TEXTURE_REPEAT_DISABLED);
+			ids_ptr[4] = storage->sampler_rd_get_default(RS::CANVAS_ITEM_TEXTURE_FILTER_NEAREST_WITH_MIPMAPS_ANISOTROPIC, RS::CANVAS_ITEM_TEXTURE_REPEAT_DISABLED);
+			ids_ptr[5] = storage->sampler_rd_get_default(RS::CANVAS_ITEM_TEXTURE_FILTER_LINEAR_WITH_MIPMAPS_ANISOTROPIC, RS::CANVAS_ITEM_TEXTURE_REPEAT_DISABLED);
+			ids_ptr[6] = storage->sampler_rd_get_default(RS::CANVAS_ITEM_TEXTURE_FILTER_NEAREST, RS::CANVAS_ITEM_TEXTURE_REPEAT_ENABLED);
+			ids_ptr[7] = storage->sampler_rd_get_default(RS::CANVAS_ITEM_TEXTURE_FILTER_LINEAR, RS::CANVAS_ITEM_TEXTURE_REPEAT_ENABLED);
+			ids_ptr[8] = storage->sampler_rd_get_default(RS::CANVAS_ITEM_TEXTURE_FILTER_NEAREST_WITH_MIPMAPS, RS::CANVAS_ITEM_TEXTURE_REPEAT_ENABLED);
+			ids_ptr[9] = storage->sampler_rd_get_default(RS::CANVAS_ITEM_TEXTURE_FILTER_LINEAR_WITH_MIPMAPS, RS::CANVAS_ITEM_TEXTURE_REPEAT_ENABLED);
+			ids_ptr[10] = storage->sampler_rd_get_default(RS::CANVAS_ITEM_TEXTURE_FILTER_NEAREST_WITH_MIPMAPS_ANISOTROPIC, RS::CANVAS_ITEM_TEXTURE_REPEAT_ENABLED);
+			ids_ptr[11] = storage->sampler_rd_get_default(RS::CANVAS_ITEM_TEXTURE_FILTER_LINEAR_WITH_MIPMAPS_ANISOTROPIC, RS::CANVAS_ITEM_TEXTURE_REPEAT_ENABLED);
+			uniforms.push_back(u);
+		}
+
+		{
+			RD::Uniform u;
+			u.binding = 2;
+			u.uniform_type = RD::UNIFORM_TYPE_SAMPLER;
+			u.ids.push_back(scene_shader.shadow_sampler);
+			uniforms.push_back(u);
+		}
+
+		{
+			RD::Uniform u;
+			u.binding = 3;
+			u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER;
+			u.ids.push_back(get_omni_light_buffer());
+			uniforms.push_back(u);
+		}
+		{
+			RD::Uniform u;
+			u.binding = 4;
+			u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER;
+			u.ids.push_back(get_spot_light_buffer());
+			uniforms.push_back(u);
+		}
+
+		{
+			RD::Uniform u;
+			u.binding = 5;
+			u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER;
+			u.ids.push_back(get_reflection_probe_buffer());
+			uniforms.push_back(u);
+		}
+		{
+			RD::Uniform u;
+			u.binding = 6;
+			u.uniform_type = RD::UNIFORM_TYPE_UNIFORM_BUFFER;
+			u.ids.push_back(get_directional_light_buffer());
+			uniforms.push_back(u);
+		}
+		{
+			RD::Uniform u;
+			u.binding = 7;
+			u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER;
+			u.ids.push_back(scene_state.lightmap_buffer);
+			uniforms.push_back(u);
+		}
+		{
+			RD::Uniform u;
+			u.binding = 8;
+			u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER;
+			u.ids.push_back(scene_state.lightmap_capture_buffer);
+			uniforms.push_back(u);
+		}
+		{
+			RD::Uniform u;
+			u.binding = 9;
+			u.uniform_type = RD::UNIFORM_TYPE_TEXTURE;
+			RID decal_atlas = storage->decal_atlas_get_texture();
+			u.ids.push_back(decal_atlas);
+			uniforms.push_back(u);
+		}
+		{
+			RD::Uniform u;
+			u.binding = 10;
+			u.uniform_type = RD::UNIFORM_TYPE_TEXTURE;
+			RID decal_atlas = storage->decal_atlas_get_texture_srgb();
+			u.ids.push_back(decal_atlas);
+			uniforms.push_back(u);
+		}
+		{
+			RD::Uniform u;
+			u.binding = 11;
+			u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER;
+			u.ids.push_back(get_decal_buffer());
+			uniforms.push_back(u);
+		}
+
+		{
+			RD::Uniform u;
+			u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER;
+			u.binding = 12;
+			u.ids.push_back(storage->global_variables_get_storage_buffer());
+			uniforms.push_back(u);
+		}
+
+		render_base_uniform_set = RD::get_singleton()->uniform_set_create(uniforms, scene_shader.default_shader_rd, SCENE_UNIFORM_SET);
+	}
+}
+
+RID RenderForwardMobile::_render_buffers_get_normal_texture(RID p_render_buffers) {
+	// RenderBufferDataForwardMobile *rb = (RenderBufferDataForwardMobile *)render_buffers_get_data(p_render_buffers);
+
+	// We don't have this. This is for debugging
+	// return rb->normal_roughness_buffer;
+	return RID();
+}
+
+void RenderForwardMobile::_fill_render_list(RenderListType p_render_list, const PagedArray<GeometryInstance *> &p_instances, PassMode p_pass_mode, const CameraMatrix &p_cam_projection, const Transform &p_cam_transform, const Plane &p_lod_plane, float p_lod_distance_multiplier, float p_screen_lod_threshold, bool p_append) {
+	if (p_render_list == RENDER_LIST_OPAQUE) {
+		scene_state.used_sss = false;
+		scene_state.used_screen_texture = false;
+		scene_state.used_normal_texture = false;
+		scene_state.used_depth_texture = false;
+	}
+	uint32_t lightmap_captures_used = 0;
+
+	Plane near_plane(p_cam_transform.origin, -p_cam_transform.basis.get_axis(Vector3::AXIS_Z));
+	near_plane.d += p_cam_projection.get_z_near();
+	float z_max = p_cam_projection.get_z_far() - p_cam_projection.get_z_near();
+
+	RenderList *rl = &render_list[p_render_list];
+
+	// Parse any updates on our geometry, updates surface caches and such
+	_update_dirty_geometry_instances();
+
+	if (!p_append) {
+		rl->clear();
+		if (p_render_list == RENDER_LIST_OPAQUE) {
+			render_list[RENDER_LIST_ALPHA].clear(); //opaque fills alpha too
+		}
+	}
+
+	//fill list
+
+	for (int i = 0; i < (int)p_instances.size(); i++) {
+		GeometryInstanceForwardMobile *inst = static_cast<GeometryInstanceForwardMobile *>(p_instances[i]);
+
+		Vector3 support_min = inst->transformed_aabb.get_support(-near_plane.normal);
+		inst->depth = near_plane.distance_to(support_min);
+		uint32_t depth_layer = CLAMP(int(inst->depth * 16 / z_max), 0, 15);
+
+		uint32_t flags = inst->base_flags; //fill flags if appropriate
+
+		bool uses_lightmap = false;
+		// bool uses_gi = false;
+
+		if (p_render_list == RENDER_LIST_OPAQUE) {
+			if (inst->lightmap_instance.is_valid()) {
+				int32_t lightmap_cull_index = -1;
+				for (uint32_t j = 0; j < scene_state.lightmaps_used; j++) {
+					if (scene_state.lightmap_ids[j] == inst->lightmap_instance) {
+						lightmap_cull_index = j;
+						break;
+					}
+				}
+				if (lightmap_cull_index >= 0) {
+					inst->gi_offset_cache = inst->lightmap_slice_index << 16;
+					inst->gi_offset_cache |= lightmap_cull_index;
+					flags |= INSTANCE_DATA_FLAG_USE_LIGHTMAP;
+					if (scene_state.lightmap_has_sh[lightmap_cull_index]) {
+						flags |= INSTANCE_DATA_FLAG_USE_SH_LIGHTMAP;
+					}
+					uses_lightmap = true;
+				} else {
+					inst->gi_offset_cache = 0xFFFFFFFF;
+				}
+
+			} else if (inst->lightmap_sh) {
+				if (lightmap_captures_used < scene_state.max_lightmap_captures) {
+					const Color *src_capture = inst->lightmap_sh->sh;
+					LightmapCaptureData &lcd = scene_state.lightmap_captures[lightmap_captures_used];
+					for (int j = 0; j < 9; j++) {
+						lcd.sh[j * 4 + 0] = src_capture[j].r;
+						lcd.sh[j * 4 + 1] = src_capture[j].g;
+						lcd.sh[j * 4 + 2] = src_capture[j].b;
+						lcd.sh[j * 4 + 3] = src_capture[j].a;
+					}
+					flags |= INSTANCE_DATA_FLAG_USE_LIGHTMAP_CAPTURE;
+					inst->gi_offset_cache = lightmap_captures_used;
+					lightmap_captures_used++;
+					uses_lightmap = true;
+				}
+			}
+		}
+		inst->flags_cache = flags;
+
+		GeometryInstanceSurfaceDataCache *surf = inst->surface_caches;
+
+		while (surf) {
+			surf->sort.uses_lightmap = 0;
+
+			// LOD
+
+			if (p_screen_lod_threshold > 0.0 && storage->mesh_surface_has_lod(surf->surface)) {
+				//lod
+				Vector3 lod_support_min = inst->transformed_aabb.get_support(-p_lod_plane.normal);
+				Vector3 lod_support_max = inst->transformed_aabb.get_support(p_lod_plane.normal);
+
+				float distance_min = p_lod_plane.distance_to(lod_support_min);
+				float distance_max = p_lod_plane.distance_to(lod_support_max);
+
+				float distance = 0.0;
+
+				if (distance_min * distance_max < 0.0) {
+					//crossing plane
+					distance = 0.0;
+				} else if (distance_min >= 0.0) {
+					distance = distance_min;
+				} else if (distance_max <= 0.0) {
+					distance = -distance_max;
+				}
+
+				surf->lod_index = storage->mesh_surface_get_lod(surf->surface, inst->lod_model_scale * inst->lod_bias, distance * p_lod_distance_multiplier, p_screen_lod_threshold);
+			} else {
+				surf->lod_index = 0;
+			}
+
+			// ADD Element
+			if (p_pass_mode == PASS_MODE_COLOR) {
+				if (surf->flags & (GeometryInstanceSurfaceDataCache::FLAG_PASS_DEPTH | GeometryInstanceSurfaceDataCache::FLAG_PASS_OPAQUE)) {
+					rl->add_element(surf);
+				}
+				if (surf->flags & GeometryInstanceSurfaceDataCache::FLAG_PASS_ALPHA) {
+					render_list[RENDER_LIST_ALPHA].add_element(surf);
+					// if (uses_gi) {
+					//	surf->sort.uses_forward_gi = 1;
+					// }
+				}
+
+				if (uses_lightmap) {
+					surf->sort.uses_lightmap = 1; // This needs to become our lightmap index but we'll do that in a separate PR.
+				}
+
+				if (surf->flags & GeometryInstanceSurfaceDataCache::FLAG_USES_SUBSURFACE_SCATTERING) {
+					scene_state.used_sss = true;
+				}
+				if (surf->flags & GeometryInstanceSurfaceDataCache::FLAG_USES_SCREEN_TEXTURE) {
+					scene_state.used_screen_texture = true;
+				}
+				if (surf->flags & GeometryInstanceSurfaceDataCache::FLAG_USES_NORMAL_TEXTURE) {
+					scene_state.used_normal_texture = true;
+				}
+				if (surf->flags & GeometryInstanceSurfaceDataCache::FLAG_USES_DEPTH_TEXTURE) {
+					scene_state.used_depth_texture = true;
+				}
+
+			} else if (p_pass_mode == PASS_MODE_SHADOW || p_pass_mode == PASS_MODE_SHADOW_DP) {
+				if (surf->flags & GeometryInstanceSurfaceDataCache::FLAG_PASS_SHADOW) {
+					rl->add_element(surf);
+				}
+			} else {
+				if (surf->flags & (GeometryInstanceSurfaceDataCache::FLAG_PASS_DEPTH | GeometryInstanceSurfaceDataCache::FLAG_PASS_OPAQUE)) {
+					rl->add_element(surf);
+				}
+			}
+
+			surf->sort.depth_layer = depth_layer;
+
+			surf = surf->next;
+		}
+	}
+}
+
+void RenderForwardMobile::_setup_environment(RID p_environment, RID p_render_buffers, const CameraMatrix &p_cam_projection, const Transform &p_cam_transform, RID p_reflection_probe, bool p_no_fog, const Size2i &p_screen_size, RID p_shadow_atlas, bool p_flip_y, const Color &p_default_bg_color, float p_znear, float p_zfar, bool p_opaque_render_buffers, bool p_pancake_shadows, int p_index) {
+	//!BAS! need to go through this and find out what we don't need anymore
+
+	// This populates our UBO with main scene data that is pushed into set 1
+
+	//CameraMatrix projection = p_cam_projection;
+	//projection.flip_y(); // Vulkan and modern APIs use Y-Down
+	CameraMatrix correction;
+	correction.set_depth_correction(p_flip_y);
+	CameraMatrix projection = correction * p_cam_projection;
+
+	//store camera into ubo
+	RendererStorageRD::store_camera(projection, scene_state.ubo.projection_matrix);
+	RendererStorageRD::store_camera(projection.inverse(), scene_state.ubo.inv_projection_matrix);
+	RendererStorageRD::store_transform(p_cam_transform, scene_state.ubo.camera_matrix);
+	RendererStorageRD::store_transform(p_cam_transform.affine_inverse(), scene_state.ubo.inv_camera_matrix);
+
+	scene_state.ubo.z_far = p_zfar;
+	scene_state.ubo.z_near = p_znear;
+
+	scene_state.ubo.pancake_shadows = p_pancake_shadows;
+
+	RendererStorageRD::store_soft_shadow_kernel(directional_penumbra_shadow_kernel_get(), scene_state.ubo.directional_penumbra_shadow_kernel);
+	RendererStorageRD::store_soft_shadow_kernel(directional_soft_shadow_kernel_get(), scene_state.ubo.directional_soft_shadow_kernel);
+	RendererStorageRD::store_soft_shadow_kernel(penumbra_shadow_kernel_get(), scene_state.ubo.penumbra_shadow_kernel);
+	RendererStorageRD::store_soft_shadow_kernel(soft_shadow_kernel_get(), scene_state.ubo.soft_shadow_kernel);
+
+	scene_state.ubo.directional_penumbra_shadow_samples = directional_penumbra_shadow_samples_get();
+	scene_state.ubo.directional_soft_shadow_samples = directional_soft_shadow_samples_get();
+	scene_state.ubo.penumbra_shadow_samples = penumbra_shadow_samples_get();
+	scene_state.ubo.soft_shadow_samples = soft_shadow_samples_get();
+
+	Size2 screen_pixel_size = Vector2(1.0, 1.0) / Size2(p_screen_size);
+	scene_state.ubo.screen_pixel_size[0] = screen_pixel_size.x;
+	scene_state.ubo.screen_pixel_size[1] = screen_pixel_size.y;
+
+	/*
+	scene_state.ubo.cluster_shift = get_shift_from_power_of_2(p_cluster_size);
+	scene_state.ubo.max_cluster_element_count_div_32 = p_max_cluster_elements / 32;
+	{
+		uint32_t cluster_screen_width = (p_screen_size.width - 1) / p_cluster_size + 1;
+		uint32_t cluster_screen_height = (p_screen_size.height - 1) / p_cluster_size + 1;
+		scene_state.ubo.cluster_type_size = cluster_screen_width * cluster_screen_height * (scene_state.ubo.max_cluster_element_count_div_32 + 32);
+		scene_state.ubo.cluster_width = cluster_screen_width;
+	}
+	*/
+
+	if (p_shadow_atlas.is_valid()) {
+		Vector2 sas = shadow_atlas_get_size(p_shadow_atlas);
+		scene_state.ubo.shadow_atlas_pixel_size[0] = 1.0 / sas.x;
+		scene_state.ubo.shadow_atlas_pixel_size[1] = 1.0 / sas.y;
+	}
+	{
+		Vector2 dss = directional_shadow_get_size();
+		scene_state.ubo.directional_shadow_pixel_size[0] = 1.0 / dss.x;
+		scene_state.ubo.directional_shadow_pixel_size[1] = 1.0 / dss.y;
+	}
+
+	//time global variables
+	scene_state.ubo.time = time;
+
+	/*
+	scene_state.ubo.gi_upscale_for_msaa = false;
+	scene_state.ubo.volumetric_fog_enabled = false;
+	scene_state.ubo.fog_enabled = false;
+
+	if (p_render_buffers.is_valid()) {
+		RenderBufferDataForwardMobile *render_buffers = (RenderBufferDataForwardMobile *)render_buffers_get_data(p_render_buffers);
+		if (render_buffers->msaa != RS::VIEWPORT_MSAA_DISABLED) {
+			scene_state.ubo.gi_upscale_for_msaa = true;
+		}
+
+		if (render_buffers_has_volumetric_fog(p_render_buffers)) {
+			scene_state.ubo.volumetric_fog_enabled = true;
+			float fog_end = render_buffers_get_volumetric_fog_end(p_render_buffers);
+			if (fog_end > 0.0) {
+				scene_state.ubo.volumetric_fog_inv_length = 1.0 / fog_end;
+			} else {
+				scene_state.ubo.volumetric_fog_inv_length = 1.0;
+			}
+
+			float fog_detail_spread = render_buffers_get_volumetric_fog_detail_spread(p_render_buffers); //reverse lookup
+			if (fog_detail_spread > 0.0) {
+				scene_state.ubo.volumetric_fog_detail_spread = 1.0 / fog_detail_spread;
+			} else {
+				scene_state.ubo.volumetric_fog_detail_spread = 1.0;
+			}
+		}
+	}
+
+	*/
+
+	if (get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_UNSHADED) {
+		scene_state.ubo.use_ambient_light = true;
+		scene_state.ubo.ambient_light_color_energy[0] = 1;
+		scene_state.ubo.ambient_light_color_energy[1] = 1;
+		scene_state.ubo.ambient_light_color_energy[2] = 1;
+		scene_state.ubo.ambient_light_color_energy[3] = 1.0;
+		scene_state.ubo.use_ambient_cubemap = false;
+		scene_state.ubo.use_reflection_cubemap = false;
+		scene_state.ubo.ssao_enabled = false;
+
+	} else if (is_environment(p_environment)) {
+		RS::EnvironmentBG env_bg = environment_get_background(p_environment);
+		RS::EnvironmentAmbientSource ambient_src = environment_get_ambient_source(p_environment);
+
+		float bg_energy = environment_get_bg_energy(p_environment);
+		scene_state.ubo.ambient_light_color_energy[3] = bg_energy;
+
+		scene_state.ubo.ambient_color_sky_mix = environment_get_ambient_sky_contribution(p_environment);
+
+		//ambient
+		if (ambient_src == RS::ENV_AMBIENT_SOURCE_BG && (env_bg == RS::ENV_BG_CLEAR_COLOR || env_bg == RS::ENV_BG_COLOR)) {
+			Color color = env_bg == RS::ENV_BG_CLEAR_COLOR ? p_default_bg_color : environment_get_bg_color(p_environment);
+			color = color.to_linear();
+
+			scene_state.ubo.ambient_light_color_energy[0] = color.r * bg_energy;
+			scene_state.ubo.ambient_light_color_energy[1] = color.g * bg_energy;
+			scene_state.ubo.ambient_light_color_energy[2] = color.b * bg_energy;
+			scene_state.ubo.use_ambient_light = true;
+			scene_state.ubo.use_ambient_cubemap = false;
+		} else {
+			float energy = environment_get_ambient_light_energy(p_environment);
+			Color color = environment_get_ambient_light_color(p_environment);
+			color = color.to_linear();
+			scene_state.ubo.ambient_light_color_energy[0] = color.r * energy;
+			scene_state.ubo.ambient_light_color_energy[1] = color.g * energy;
+			scene_state.ubo.ambient_light_color_energy[2] = color.b * energy;
+
+			Basis sky_transform = environment_get_sky_orientation(p_environment);
+			sky_transform = sky_transform.inverse() * p_cam_transform.basis;
+			RendererStorageRD::store_transform_3x3(sky_transform, scene_state.ubo.radiance_inverse_xform);
+
+			scene_state.ubo.use_ambient_cubemap = (ambient_src == RS::ENV_AMBIENT_SOURCE_BG && env_bg == RS::ENV_BG_SKY) || ambient_src == RS::ENV_AMBIENT_SOURCE_SKY;
+			scene_state.ubo.use_ambient_light = scene_state.ubo.use_ambient_cubemap || ambient_src == RS::ENV_AMBIENT_SOURCE_COLOR;
+		}
+
+		//specular
+		RS::EnvironmentReflectionSource ref_src = environment_get_reflection_source(p_environment);
+		if ((ref_src == RS::ENV_REFLECTION_SOURCE_BG && env_bg == RS::ENV_BG_SKY) || ref_src == RS::ENV_REFLECTION_SOURCE_SKY) {
+			scene_state.ubo.use_reflection_cubemap = true;
+		} else {
+			scene_state.ubo.use_reflection_cubemap = false;
+		}
+
+		scene_state.ubo.ssao_enabled = p_opaque_render_buffers && environment_is_ssao_enabled(p_environment);
+		scene_state.ubo.ssao_ao_affect = environment_get_ssao_ao_affect(p_environment);
+		scene_state.ubo.ssao_light_affect = environment_get_ssao_light_affect(p_environment);
+
+		Color ao_color = environment_get_ao_color(p_environment).to_linear();
+		scene_state.ubo.ao_color[0] = ao_color.r;
+		scene_state.ubo.ao_color[1] = ao_color.g;
+		scene_state.ubo.ao_color[2] = ao_color.b;
+		scene_state.ubo.ao_color[3] = ao_color.a;
+
+		scene_state.ubo.fog_enabled = environment_is_fog_enabled(p_environment);
+		scene_state.ubo.fog_density = environment_get_fog_density(p_environment);
+		scene_state.ubo.fog_height = environment_get_fog_height(p_environment);
+		scene_state.ubo.fog_height_density = environment_get_fog_height_density(p_environment);
+		if (scene_state.ubo.fog_height_density >= 0.0001) {
+			scene_state.ubo.fog_height_density = 1.0 / scene_state.ubo.fog_height_density;
+		}
+		scene_state.ubo.fog_aerial_perspective = environment_get_fog_aerial_perspective(p_environment);
+
+		Color fog_color = environment_get_fog_light_color(p_environment).to_linear();
+		float fog_energy = environment_get_fog_light_energy(p_environment);
+
+		scene_state.ubo.fog_light_color[0] = fog_color.r * fog_energy;
+		scene_state.ubo.fog_light_color[1] = fog_color.g * fog_energy;
+		scene_state.ubo.fog_light_color[2] = fog_color.b * fog_energy;
+
+		scene_state.ubo.fog_sun_scatter = environment_get_fog_sun_scatter(p_environment);
+
+	} else {
+		if (p_reflection_probe.is_valid() && storage->reflection_probe_is_interior(reflection_probe_instance_get_probe(p_reflection_probe))) {
+			scene_state.ubo.use_ambient_light = false;
+		} else {
+			scene_state.ubo.use_ambient_light = true;
+			Color clear_color = p_default_bg_color;
+			clear_color = clear_color.to_linear();
+			scene_state.ubo.ambient_light_color_energy[0] = clear_color.r;
+			scene_state.ubo.ambient_light_color_energy[1] = clear_color.g;
+			scene_state.ubo.ambient_light_color_energy[2] = clear_color.b;
+			scene_state.ubo.ambient_light_color_energy[3] = 1.0;
+		}
+
+		scene_state.ubo.use_ambient_cubemap = false;
+		scene_state.ubo.use_reflection_cubemap = false;
+		scene_state.ubo.ssao_enabled = false;
+	}
+
+	scene_state.ubo.roughness_limiter_enabled = p_opaque_render_buffers && screen_space_roughness_limiter_is_active();
+	scene_state.ubo.roughness_limiter_amount = screen_space_roughness_limiter_get_amount();
+	scene_state.ubo.roughness_limiter_limit = screen_space_roughness_limiter_get_limit();
+
+	if (p_index >= (int)scene_state.uniform_buffers.size()) {
+		uint32_t from = scene_state.uniform_buffers.size();
+		scene_state.uniform_buffers.resize(p_index + 1);
+		render_pass_uniform_sets.resize(p_index + 1);
+		for (uint32_t i = from; i < scene_state.uniform_buffers.size(); i++) {
+			scene_state.uniform_buffers[i] = RD::get_singleton()->uniform_buffer_create(sizeof(SceneState::UBO));
+		}
+	}
+	RD::get_singleton()->buffer_update(scene_state.uniform_buffers[p_index], 0, sizeof(SceneState::UBO), &scene_state.ubo, RD::BARRIER_MASK_RASTER);
+}
+
+void RenderForwardMobile::_fill_instance_data(RenderListType p_render_list, uint32_t p_offset, int32_t p_max_elements, bool p_update_buffer) {
+	// !BAS! Rename this to make clear this is not the same as with the forward renderer and remove p_update_buffer?
+
+	RenderList *rl = &render_list[p_render_list];
+	uint32_t element_total = p_max_elements >= 0 ? uint32_t(p_max_elements) : rl->elements.size();
+
+	rl->element_info.resize(p_offset + element_total);
+
+	uint32_t repeats = 0;
+	GeometryInstanceSurfaceDataCache *prev_surface = nullptr;
+	for (uint32_t i = 0; i < element_total; i++) {
+		GeometryInstanceSurfaceDataCache *surface = rl->elements[i + p_offset];
+		GeometryInstanceForwardMobile *inst = surface->owner;
+
+		bool cant_repeat = inst->flags_cache & INSTANCE_DATA_FLAG_MULTIMESH || inst->mesh_instance.is_valid();
+
+		if (prev_surface != nullptr && !cant_repeat && prev_surface->sort.sort_key1 == surface->sort.sort_key1 && prev_surface->sort.sort_key2 == surface->sort.sort_key2) {
+			//this element is the same as the previous one, count repeats to draw it using instancing
+			repeats++;
+		} else {
+			if (repeats > 0) {
+				for (uint32_t j = 1; j <= repeats; j++) {
+					rl->element_info[p_offset + i - j].repeat = j;
+				}
+			}
+			repeats = 1;
+		}
+
+		RenderElementInfo &element_info = rl->element_info[p_offset + i];
+
+		element_info.lod_index = surface->lod_index;
+		element_info.uses_lightmap = surface->sort.uses_lightmap;
+
+		if (cant_repeat) {
+			prev_surface = nullptr;
+		} else {
+			prev_surface = surface;
+		}
+	}
+
+	if (repeats > 0) {
+		for (uint32_t j = 1; j <= repeats; j++) {
+			rl->element_info[p_offset + element_total - j].repeat = j;
+		}
+	}
+}
+
+/// RENDERING ///
+
+void RenderForwardMobile::_render_list(RenderingDevice::DrawListID p_draw_list, RenderingDevice::FramebufferFormatID p_framebuffer_Format, RenderListParameters *p_params, uint32_t p_from_element, uint32_t p_to_element) {
+	//use template for faster performance (pass mode comparisons are inlined)
+
+	switch (p_params->pass_mode) {
+		case PASS_MODE_COLOR: {
+			_render_list_template<PASS_MODE_COLOR>(p_draw_list, p_framebuffer_Format, p_params, p_from_element, p_to_element);
+		} break;
+		case PASS_MODE_COLOR_TRANSPARENT: {
+			_render_list_template<PASS_MODE_COLOR_TRANSPARENT>(p_draw_list, p_framebuffer_Format, p_params, p_from_element, p_to_element);
+		} break;
+		case PASS_MODE_SHADOW: {
+			_render_list_template<PASS_MODE_SHADOW>(p_draw_list, p_framebuffer_Format, p_params, p_from_element, p_to_element);
+		} break;
+		case PASS_MODE_SHADOW_DP: {
+			_render_list_template<PASS_MODE_SHADOW_DP>(p_draw_list, p_framebuffer_Format, p_params, p_from_element, p_to_element);
+		} break;
+		case PASS_MODE_DEPTH_MATERIAL: {
+			_render_list_template<PASS_MODE_DEPTH_MATERIAL>(p_draw_list, p_framebuffer_Format, p_params, p_from_element, p_to_element);
+		} break;
+	}
+}
+
+void RenderForwardMobile::_render_list_thread_function(uint32_t p_thread, RenderListParameters *p_params) {
+	uint32_t render_total = p_params->element_count;
+	uint32_t total_threads = RendererThreadPool::singleton->thread_work_pool.get_thread_count();
+	uint32_t render_from = p_thread * render_total / total_threads;
+	uint32_t render_to = (p_thread + 1 == total_threads) ? render_total : ((p_thread + 1) * render_total / total_threads);
+	_render_list(thread_draw_lists[p_thread], p_params->framebuffer_format, p_params, render_from, render_to);
+}
+
+void RenderForwardMobile::_render_list_with_threads(RenderListParameters *p_params, RID p_framebuffer, RD::InitialAction p_initial_color_action, RD::FinalAction p_final_color_action, RD::InitialAction p_initial_depth_action, RD::FinalAction p_final_depth_action, const Vector<Color> &p_clear_color_values, float p_clear_depth, uint32_t p_clear_stencil, const Rect2 &p_region, const Vector<RID> &p_storage_textures) {
+	RD::FramebufferFormatID fb_format = RD::get_singleton()->framebuffer_get_format(p_framebuffer);
+	p_params->framebuffer_format = fb_format;
+
+	if ((uint32_t)p_params->element_count > render_list_thread_threshold && false) { // secondary command buffers need more testing at this time
+		//multi threaded
+		thread_draw_lists.resize(RendererThreadPool::singleton->thread_work_pool.get_thread_count());
+		RD::get_singleton()->draw_list_begin_split(p_framebuffer, thread_draw_lists.size(), thread_draw_lists.ptr(), p_initial_color_action, p_final_color_action, p_initial_depth_action, p_final_depth_action, p_clear_color_values, p_clear_depth, p_clear_stencil, p_region, p_storage_textures);
+		RendererThreadPool::singleton->thread_work_pool.do_work(thread_draw_lists.size(), this, &RenderForwardMobile::_render_list_thread_function, p_params);
+		RD::get_singleton()->draw_list_end(p_params->barrier);
+	} else {
+		//single threaded
+		RD::DrawListID draw_list = RD::get_singleton()->draw_list_begin(p_framebuffer, p_initial_color_action, p_final_color_action, p_initial_depth_action, p_final_depth_action, p_clear_color_values, p_clear_depth, p_clear_stencil, p_region, p_storage_textures);
+		_render_list(draw_list, fb_format, p_params, 0, p_params->element_count);
+		RD::get_singleton()->draw_list_end(p_params->barrier);
+	}
+}
+
+template <RenderForwardMobile::PassMode p_pass_mode>
+void RenderForwardMobile::_render_list_template(RenderingDevice::DrawListID p_draw_list, RenderingDevice::FramebufferFormatID p_framebuffer_Format, RenderListParameters *p_params, uint32_t p_from_element, uint32_t p_to_element) {
+	RD::DrawListID draw_list = p_draw_list;
+	RD::FramebufferFormatID framebuffer_format = p_framebuffer_Format;
+
+	//global scope bindings
+	RD::get_singleton()->draw_list_bind_uniform_set(draw_list, render_base_uniform_set, SCENE_UNIFORM_SET);
+	RD::get_singleton()->draw_list_bind_uniform_set(draw_list, p_params->render_pass_uniform_set, RENDER_PASS_UNIFORM_SET);
+	RD::get_singleton()->draw_list_bind_uniform_set(draw_list, scene_shader.default_vec4_xform_uniform_set, TRANSFORMS_UNIFORM_SET);
+
+	RID prev_material_uniform_set;
+
+	RID prev_vertex_array_rd;
+	RID prev_index_array_rd;
+	RID prev_pipeline_rd;
+	RID prev_xforms_uniform_set;
+
+	bool shadow_pass = (p_params->pass_mode == PASS_MODE_SHADOW) || (p_params->pass_mode == PASS_MODE_SHADOW_DP);
+
+	for (uint32_t i = p_from_element; i < p_to_element; i++) {
+		const GeometryInstanceSurfaceDataCache *surf = p_params->elements[i];
+		const RenderElementInfo &element_info = p_params->element_info[i];
+		const GeometryInstanceForwardMobile *inst = surf->owner;
+
+		// GeometryInstanceForwardMobile::PushConstant push_constant = inst->push_constant;
+		GeometryInstanceForwardMobile::PushConstant push_constant;
+
+		if (inst->store_transform_cache) {
+			RendererStorageRD::store_transform(inst->transform, push_constant.transform);
+		} else {
+			RendererStorageRD::store_transform(Transform(), push_constant.transform);
+		}
+
+		push_constant.flags = inst->flags_cache;
+		push_constant.gi_offset = inst->gi_offset_cache;
+		push_constant.layer_mask = inst->layer_mask;
+		push_constant.instance_uniforms_ofs = uint32_t(inst->shader_parameters_offset);
+
+		if (p_params->pass_mode == PASS_MODE_DEPTH_MATERIAL) {
+			// abuse lightmap_uv_scale[0] here, should not be needed here
+			push_constant.lightmap_uv_scale[0] = p_params->uv_offset.x;
+			push_constant.lightmap_uv_scale[1] = p_params->uv_offset.y;
+		} else {
+			push_constant.lightmap_uv_scale[0] = inst->lightmap_uv_scale.position.x;
+			push_constant.lightmap_uv_scale[1] = inst->lightmap_uv_scale.position.y;
+			push_constant.lightmap_uv_scale[2] = inst->lightmap_uv_scale.size.x;
+			push_constant.lightmap_uv_scale[3] = inst->lightmap_uv_scale.size.y;
+		};
+
+		_fill_instance_indices(inst->omni_lights, inst->omni_light_count, push_constant.omni_lights, inst->spot_lights, inst->spot_light_count, push_constant.spot_lights, inst->reflection_probes, inst->reflection_probe_count, push_constant.reflection_probes, inst->decals, inst->decals_count, push_constant.decals, push_constant.layer_mask);
+
+		RID material_uniform_set;
+		SceneShaderForwardMobile::ShaderData *shader;
+		void *mesh_surface;
+
+		if (shadow_pass) {
+			material_uniform_set = surf->material_uniform_set_shadow;
+			shader = surf->shader_shadow;
+			mesh_surface = surf->surface_shadow;
+
+		} else {
+			material_uniform_set = surf->material_uniform_set;
+			shader = surf->shader;
+			mesh_surface = surf->surface;
+		}
+
+		if (!mesh_surface) {
+			continue;
+		}
+
+		//find cull variant
+		SceneShaderForwardMobile::ShaderData::CullVariant cull_variant;
+
+		if (p_params->pass_mode == PASS_MODE_DEPTH_MATERIAL || ((p_params->pass_mode == PASS_MODE_SHADOW || p_params->pass_mode == PASS_MODE_SHADOW_DP) && surf->flags & GeometryInstanceSurfaceDataCache::FLAG_USES_DOUBLE_SIDED_SHADOWS)) {
+			cull_variant = SceneShaderForwardMobile::ShaderData::CULL_VARIANT_DOUBLE_SIDED;
+		} else {
+			bool mirror = surf->owner->mirror;
+			if (p_params->reverse_cull) {
+				mirror = !mirror;
+			}
+			cull_variant = mirror ? SceneShaderForwardMobile::ShaderData::CULL_VARIANT_REVERSED : SceneShaderForwardMobile::ShaderData::CULL_VARIANT_NORMAL;
+		}
+
+		RS::PrimitiveType primitive = surf->primitive;
+		RID xforms_uniform_set = surf->owner->transforms_uniform_set;
+
+		SceneShaderForwardMobile::ShaderVersion shader_version = SceneShaderForwardMobile::SHADER_VERSION_MAX; // Assigned to silence wrong -Wmaybe-initialized.
+
+		switch (p_params->pass_mode) {
+			case PASS_MODE_COLOR:
+			case PASS_MODE_COLOR_TRANSPARENT: {
+				if (element_info.uses_lightmap) {
+					shader_version = SceneShaderForwardMobile::SHADER_VERSION_LIGHTMAP_COLOR_PASS;
+				} else {
+					shader_version = SceneShaderForwardMobile::SHADER_VERSION_COLOR_PASS;
+				}
+			} break;
+			case PASS_MODE_SHADOW: {
+				shader_version = SceneShaderForwardMobile::SHADER_VERSION_SHADOW_PASS;
+			} break;
+			case PASS_MODE_SHADOW_DP: {
+				shader_version = SceneShaderForwardMobile::SHADER_VERSION_DEPTH_PASS_DP;
+			} break;
+			case PASS_MODE_DEPTH_MATERIAL: {
+				shader_version = SceneShaderForwardMobile::SHADER_VERSION_DEPTH_PASS_WITH_MATERIAL;
+			} break;
+		}
+
+		PipelineCacheRD *pipeline = nullptr;
+
+		pipeline = &shader->pipelines[cull_variant][primitive][shader_version];
+
+		RD::VertexFormatID vertex_format = -1;
+		RID vertex_array_rd;
+		RID index_array_rd;
+
+		//skeleton and blend shape
+		if (surf->owner->mesh_instance.is_valid()) {
+			storage->mesh_instance_surface_get_vertex_arrays_and_format(surf->owner->mesh_instance, surf->surface_index, pipeline->get_vertex_input_mask(), vertex_array_rd, vertex_format);
+		} else {
+			storage->mesh_surface_get_vertex_arrays_and_format(mesh_surface, pipeline->get_vertex_input_mask(), vertex_array_rd, vertex_format);
+		}
+
+		index_array_rd = storage->mesh_surface_get_index_array(mesh_surface, element_info.lod_index);
+
+		if (prev_vertex_array_rd != vertex_array_rd) {
+			RD::get_singleton()->draw_list_bind_vertex_array(draw_list, vertex_array_rd);
+			prev_vertex_array_rd = vertex_array_rd;
+		}
+
+		if (prev_index_array_rd != index_array_rd) {
+			if (index_array_rd.is_valid()) {
+				RD::get_singleton()->draw_list_bind_index_array(draw_list, index_array_rd);
+			}
+			prev_index_array_rd = index_array_rd;
+		}
+
+		RID pipeline_rd = pipeline->get_render_pipeline(vertex_format, framebuffer_format, p_params->force_wireframe);
+
+		if (pipeline_rd != prev_pipeline_rd) {
+			// checking with prev shader does not make so much sense, as
+			// the pipeline may still be different.
+			RD::get_singleton()->draw_list_bind_render_pipeline(draw_list, pipeline_rd);
+			prev_pipeline_rd = pipeline_rd;
+		}
+
+		if (xforms_uniform_set.is_valid() && prev_xforms_uniform_set != xforms_uniform_set) {
+			RD::get_singleton()->draw_list_bind_uniform_set(draw_list, xforms_uniform_set, TRANSFORMS_UNIFORM_SET);
+			prev_xforms_uniform_set = xforms_uniform_set;
+		}
+
+		if (material_uniform_set != prev_material_uniform_set) {
+			//update uniform set
+			if (material_uniform_set.is_valid()) {
+				RD::get_singleton()->draw_list_bind_uniform_set(draw_list, material_uniform_set, MATERIAL_UNIFORM_SET);
+			}
+
+			prev_material_uniform_set = material_uniform_set;
+		}
+
+		RD::get_singleton()->draw_list_set_push_constant(draw_list, &push_constant, sizeof(GeometryInstanceForwardMobile::PushConstant));
+
+		uint32_t instance_count = surf->owner->instance_count > 1 ? surf->owner->instance_count : element_info.repeat;
+		if (surf->flags & GeometryInstanceSurfaceDataCache::FLAG_USES_PARTICLE_TRAILS) {
+			instance_count /= surf->owner->trail_steps;
+		}
+
+		RD::get_singleton()->draw_list_draw(draw_list, index_array_rd.is_valid(), instance_count);
+		i += element_info.repeat - 1; //skip equal elements
+	}
+}
+
+/* Geometry instance */
+
+RendererSceneRender::GeometryInstance *RenderForwardMobile::geometry_instance_create(RID p_base) {
+	RS::InstanceType type = storage->get_base_type(p_base);
+	ERR_FAIL_COND_V(!((1 << type) & RS::INSTANCE_GEOMETRY_MASK), nullptr);
+
+	GeometryInstanceForwardMobile *ginstance = geometry_instance_alloc.alloc();
+	ginstance->data = memnew(GeometryInstanceForwardMobile::Data);
+
+	ginstance->data->base = p_base;
+	ginstance->data->base_type = type;
+
+	_geometry_instance_mark_dirty(ginstance);
+
+	return ginstance;
+}
+
+void RenderForwardMobile::geometry_instance_set_skeleton(GeometryInstance *p_geometry_instance, RID p_skeleton) {
+	GeometryInstanceForwardMobile *ginstance = static_cast<GeometryInstanceForwardMobile *>(p_geometry_instance);
+	ERR_FAIL_COND(!ginstance);
+	ginstance->data->skeleton = p_skeleton;
+
+	_geometry_instance_mark_dirty(ginstance);
+	ginstance->data->dirty_dependencies = true;
+}
+
+void RenderForwardMobile::geometry_instance_set_material_override(GeometryInstance *p_geometry_instance, RID p_override) {
+	GeometryInstanceForwardMobile *ginstance = static_cast<GeometryInstanceForwardMobile *>(p_geometry_instance);
+	ERR_FAIL_COND(!ginstance);
+	ginstance->data->material_override = p_override;
+
+	_geometry_instance_mark_dirty(ginstance);
+	ginstance->data->dirty_dependencies = true;
+}
+
+void RenderForwardMobile::geometry_instance_set_surface_materials(GeometryInstance *p_geometry_instance, const Vector<RID> &p_materials) {
+	GeometryInstanceForwardMobile *ginstance = static_cast<GeometryInstanceForwardMobile *>(p_geometry_instance);
+	ERR_FAIL_COND(!ginstance);
+	ginstance->data->surface_materials = p_materials;
+
+	_geometry_instance_mark_dirty(ginstance);
+	ginstance->data->dirty_dependencies = true;
+}
+
+void RenderForwardMobile::geometry_instance_set_mesh_instance(GeometryInstance *p_geometry_instance, RID p_mesh_instance) {
+	GeometryInstanceForwardMobile *ginstance = static_cast<GeometryInstanceForwardMobile *>(p_geometry_instance);
+	ERR_FAIL_COND(!ginstance);
+	ginstance->mesh_instance = p_mesh_instance;
+
+	_geometry_instance_mark_dirty(ginstance);
+}
+
+void RenderForwardMobile::geometry_instance_set_transform(GeometryInstance *p_geometry_instance, const Transform &p_transform, const AABB &p_aabb, const AABB &p_transformed_aabb) {
+	GeometryInstanceForwardMobile *ginstance = static_cast<GeometryInstanceForwardMobile *>(p_geometry_instance);
+	ERR_FAIL_COND(!ginstance);
+	ginstance->transform = p_transform;
+	ginstance->mirror = p_transform.basis.determinant() < 0;
+	ginstance->data->aabb = p_aabb;
+	ginstance->transformed_aabb = p_transformed_aabb;
+
+	Vector3 model_scale_vec = p_transform.basis.get_scale_abs();
+	// handle non uniform scale here
+
+	float max_scale = MAX(model_scale_vec.x, MAX(model_scale_vec.y, model_scale_vec.z));
+	float min_scale = MIN(model_scale_vec.x, MIN(model_scale_vec.y, model_scale_vec.z));
+	ginstance->non_uniform_scale = max_scale >= 0.0 && (min_scale / max_scale) < 0.9;
+
+	ginstance->lod_model_scale = max_scale;
+}
+
+void RenderForwardMobile::geometry_instance_set_layer_mask(GeometryInstance *p_geometry_instance, uint32_t p_layer_mask) {
+	GeometryInstanceForwardMobile *ginstance = static_cast<GeometryInstanceForwardMobile *>(p_geometry_instance);
+	ERR_FAIL_COND(!ginstance);
+	ginstance->layer_mask = p_layer_mask;
+}
+
+void RenderForwardMobile::geometry_instance_set_lod_bias(GeometryInstance *p_geometry_instance, float p_lod_bias) {
+	GeometryInstanceForwardMobile *ginstance = static_cast<GeometryInstanceForwardMobile *>(p_geometry_instance);
+	ERR_FAIL_COND(!ginstance);
+	ginstance->lod_bias = p_lod_bias;
+}
+
+void RenderForwardMobile::geometry_instance_set_use_baked_light(GeometryInstance *p_geometry_instance, bool p_enable) {
+	GeometryInstanceForwardMobile *ginstance = static_cast<GeometryInstanceForwardMobile *>(p_geometry_instance);
+	ERR_FAIL_COND(!ginstance);
+	ginstance->data->use_baked_light = p_enable;
+
+	_geometry_instance_mark_dirty(ginstance);
+}
+
+void RenderForwardMobile::geometry_instance_set_use_dynamic_gi(GeometryInstance *p_geometry_instance, bool p_enable) {
+	// !BAS! do we support this in mobile?
+	// GeometryInstanceForwardMobile *ginstance = static_cast<GeometryInstanceForwardMobile *>(p_geometry_instance);
+	// ERR_FAIL_COND(!ginstance);
+	// ginstance->data->use_dynamic_gi = p_enable;
+	// _geometry_instance_mark_dirty(ginstance);
+}
+
+void RenderForwardMobile::geometry_instance_set_use_lightmap(GeometryInstance *p_geometry_instance, RID p_lightmap_instance, const Rect2 &p_lightmap_uv_scale, int p_lightmap_slice_index) {
+	GeometryInstanceForwardMobile *ginstance = static_cast<GeometryInstanceForwardMobile *>(p_geometry_instance);
+	ERR_FAIL_COND(!ginstance);
+	ginstance->lightmap_instance = p_lightmap_instance;
+	ginstance->lightmap_uv_scale = p_lightmap_uv_scale;
+	ginstance->lightmap_slice_index = p_lightmap_slice_index;
+	_geometry_instance_mark_dirty(ginstance);
+}
+
+void RenderForwardMobile::geometry_instance_set_lightmap_capture(GeometryInstance *p_geometry_instance, const Color *p_sh9) {
+	GeometryInstanceForwardMobile *ginstance = static_cast<GeometryInstanceForwardMobile *>(p_geometry_instance);
+	ERR_FAIL_COND(!ginstance);
+	if (p_sh9) {
+		if (ginstance->lightmap_sh == nullptr) {
+			ginstance->lightmap_sh = geometry_instance_lightmap_sh.alloc();
+		}
+
+		memcpy(ginstance->lightmap_sh->sh, p_sh9, sizeof(Color) * 9);
+	} else {
+		if (ginstance->lightmap_sh != nullptr) {
+			geometry_instance_lightmap_sh.free(ginstance->lightmap_sh);
+			ginstance->lightmap_sh = nullptr;
+		}
+	}
+	_geometry_instance_mark_dirty(ginstance);
+}
+
+void RenderForwardMobile::geometry_instance_set_instance_shader_parameters_offset(GeometryInstance *p_geometry_instance, int32_t p_offset) {
+	GeometryInstanceForwardMobile *ginstance = static_cast<GeometryInstanceForwardMobile *>(p_geometry_instance);
+	ERR_FAIL_COND(!ginstance);
+	ginstance->shader_parameters_offset = p_offset;
+	_geometry_instance_mark_dirty(ginstance);
+}
+
+void RenderForwardMobile::geometry_instance_set_cast_double_sided_shadows(GeometryInstance *p_geometry_instance, bool p_enable) {
+	GeometryInstanceForwardMobile *ginstance = static_cast<GeometryInstanceForwardMobile *>(p_geometry_instance);
+	ERR_FAIL_COND(!ginstance);
+
+	ginstance->data->cast_double_sided_shadows = p_enable;
+	_geometry_instance_mark_dirty(ginstance);
+}
+
+Transform RenderForwardMobile::geometry_instance_get_transform(GeometryInstance *p_instance) {
+	GeometryInstanceForwardMobile *ginstance = static_cast<GeometryInstanceForwardMobile *>(p_instance);
+	ERR_FAIL_COND_V(!ginstance, Transform());
+	return ginstance->transform;
+}
+
+AABB RenderForwardMobile::geometry_instance_get_aabb(GeometryInstance *p_instance) {
+	GeometryInstanceForwardMobile *ginstance = static_cast<GeometryInstanceForwardMobile *>(p_instance);
+	ERR_FAIL_COND_V(!ginstance, AABB());
+	return ginstance->data->aabb;
+}
+
+void RenderForwardMobile::geometry_instance_free(GeometryInstance *p_geometry_instance) {
+	GeometryInstanceForwardMobile *ginstance = static_cast<GeometryInstanceForwardMobile *>(p_geometry_instance);
+	ERR_FAIL_COND(!ginstance);
+	if (ginstance->lightmap_sh != nullptr) {
+		geometry_instance_lightmap_sh.free(ginstance->lightmap_sh);
+	}
+	GeometryInstanceSurfaceDataCache *surf = ginstance->surface_caches;
+	while (surf) {
+		GeometryInstanceSurfaceDataCache *next = surf->next;
+		geometry_instance_surface_alloc.free(surf);
+		surf = next;
+	}
+	memdelete(ginstance->data);
+	geometry_instance_alloc.free(ginstance);
+}
+
+uint32_t RenderForwardMobile::geometry_instance_get_pair_mask() {
+	return ((1 << RS::INSTANCE_LIGHT) + (1 << RS::INSTANCE_REFLECTION_PROBE) + (1 << RS::INSTANCE_DECAL));
+}
+
+void RenderForwardMobile::geometry_instance_pair_light_instances(GeometryInstance *p_geometry_instance, const RID *p_light_instances, uint32_t p_light_instance_count) {
+	GeometryInstanceForwardMobile *ginstance = static_cast<GeometryInstanceForwardMobile *>(p_geometry_instance);
+	ERR_FAIL_COND(!ginstance);
+
+	ginstance->omni_light_count = 0;
+	ginstance->spot_light_count = 0;
+
+	for (uint32_t i = 0; i < p_light_instance_count; i++) {
+		RS::LightType type = light_instance_get_type(p_light_instances[i]);
+		switch (type) {
+			case RS::LIGHT_OMNI: {
+				if (ginstance->omni_light_count < (uint32_t)MAX_RDL_CULL) {
+					ginstance->omni_lights[ginstance->omni_light_count] = p_light_instances[i];
+					ginstance->omni_light_count++;
+				}
+			} break;
+			case RS::LIGHT_SPOT: {
+				if (ginstance->spot_light_count < (uint32_t)MAX_RDL_CULL) {
+					ginstance->spot_lights[ginstance->spot_light_count] = p_light_instances[i];
+					ginstance->spot_light_count++;
+				}
+			} break;
+			default:
+				break;
+		}
+	}
+}
+
+void RenderForwardMobile::geometry_instance_pair_reflection_probe_instances(GeometryInstance *p_geometry_instance, const RID *p_reflection_probe_instances, uint32_t p_reflection_probe_instance_count) {
+	GeometryInstanceForwardMobile *ginstance = static_cast<GeometryInstanceForwardMobile *>(p_geometry_instance);
+	ERR_FAIL_COND(!ginstance);
+
+	ginstance->reflection_probe_count = p_reflection_probe_instance_count < (uint32_t)MAX_RDL_CULL ? p_reflection_probe_instance_count : (uint32_t)MAX_RDL_CULL;
+	for (uint32_t i = 0; i < ginstance->reflection_probe_count; i++) {
+		ginstance->reflection_probes[i] = p_reflection_probe_instances[i];
+	}
+}
+
+void RenderForwardMobile::geometry_instance_pair_decal_instances(GeometryInstance *p_geometry_instance, const RID *p_decal_instances, uint32_t p_decal_instance_count) {
+	GeometryInstanceForwardMobile *ginstance = static_cast<GeometryInstanceForwardMobile *>(p_geometry_instance);
+	ERR_FAIL_COND(!ginstance);
+
+	ginstance->decals_count = p_decal_instance_count < (uint32_t)MAX_RDL_CULL ? p_decal_instance_count : (uint32_t)MAX_RDL_CULL;
+	for (uint32_t i = 0; i < ginstance->decals_count; i++) {
+		ginstance->decals[i] = p_decal_instances[i];
+	}
+}
+
+void RenderForwardMobile::geometry_instance_pair_gi_probe_instances(GeometryInstance *p_geometry_instance, const RID *p_gi_probe_instances, uint32_t p_gi_probe_instance_count) {
+	// We do not have this here!
+}
+
+void RenderForwardMobile::_geometry_instance_mark_dirty(GeometryInstance *p_geometry_instance) {
+	GeometryInstanceForwardMobile *ginstance = static_cast<GeometryInstanceForwardMobile *>(p_geometry_instance);
+	if (ginstance->dirty_list_element.in_list()) {
+		return;
+	}
+
+	//clear surface caches
+	GeometryInstanceSurfaceDataCache *surf = ginstance->surface_caches;
+
+	while (surf) {
+		GeometryInstanceSurfaceDataCache *next = surf->next;
+		geometry_instance_surface_alloc.free(surf);
+		surf = next;
+	}
+
+	ginstance->surface_caches = nullptr;
+
+	geometry_instance_dirty_list.add(&ginstance->dirty_list_element);
+}
+
+void RenderForwardMobile::_geometry_instance_add_surface_with_material(GeometryInstanceForwardMobile *ginstance, uint32_t p_surface, SceneShaderForwardMobile::MaterialData *p_material, uint32_t p_material_id, uint32_t p_shader_id, RID p_mesh) {
+	bool has_read_screen_alpha = p_material->shader_data->uses_screen_texture || p_material->shader_data->uses_depth_texture || p_material->shader_data->uses_normal_texture;
+	bool has_base_alpha = (p_material->shader_data->uses_alpha || has_read_screen_alpha);
+	bool has_blend_alpha = p_material->shader_data->uses_blend_alpha;
+	bool has_alpha = has_base_alpha || has_blend_alpha;
+
+	uint32_t flags = 0;
+
+	if (p_material->shader_data->uses_sss) {
+		flags |= GeometryInstanceSurfaceDataCache::FLAG_USES_SUBSURFACE_SCATTERING;
+	}
+
+	if (p_material->shader_data->uses_screen_texture) {
+		flags |= GeometryInstanceSurfaceDataCache::FLAG_USES_SCREEN_TEXTURE;
+	}
+
+	if (p_material->shader_data->uses_depth_texture) {
+		flags |= GeometryInstanceSurfaceDataCache::FLAG_USES_DEPTH_TEXTURE;
+	}
+
+	if (p_material->shader_data->uses_normal_texture) {
+		flags |= GeometryInstanceSurfaceDataCache::FLAG_USES_NORMAL_TEXTURE;
+	}
+
+	if (ginstance->data->cast_double_sided_shadows) {
+		flags |= GeometryInstanceSurfaceDataCache::FLAG_USES_DOUBLE_SIDED_SHADOWS;
+	}
+
+	if (has_alpha || has_read_screen_alpha || p_material->shader_data->depth_draw == SceneShaderForwardMobile::ShaderData::DEPTH_DRAW_DISABLED || p_material->shader_data->depth_test == SceneShaderForwardMobile::ShaderData::DEPTH_TEST_DISABLED) {
+		//material is only meant for alpha pass
+		flags |= GeometryInstanceSurfaceDataCache::FLAG_PASS_ALPHA;
+		if (p_material->shader_data->uses_depth_pre_pass && !(p_material->shader_data->depth_draw == SceneShaderForwardMobile::ShaderData::DEPTH_DRAW_DISABLED || p_material->shader_data->depth_test == SceneShaderForwardMobile::ShaderData::DEPTH_TEST_DISABLED)) {
+			flags |= GeometryInstanceSurfaceDataCache::FLAG_PASS_DEPTH;
+			flags |= GeometryInstanceSurfaceDataCache::FLAG_PASS_SHADOW;
+		}
+	} else {
+		flags |= GeometryInstanceSurfaceDataCache::FLAG_PASS_OPAQUE;
+		flags |= GeometryInstanceSurfaceDataCache::FLAG_PASS_DEPTH;
+		flags |= GeometryInstanceSurfaceDataCache::FLAG_PASS_SHADOW;
+	}
+
+	if (p_material->shader_data->uses_particle_trails) {
+		flags |= GeometryInstanceSurfaceDataCache::FLAG_USES_PARTICLE_TRAILS;
+	}
+
+	SceneShaderForwardMobile::MaterialData *material_shadow = nullptr;
+	void *surface_shadow = nullptr;
+	if (!p_material->shader_data->uses_particle_trails && !p_material->shader_data->writes_modelview_or_projection && !p_material->shader_data->uses_vertex && !p_material->shader_data->uses_discard && !p_material->shader_data->uses_depth_pre_pass) {
+		flags |= GeometryInstanceSurfaceDataCache::FLAG_USES_SHARED_SHADOW_MATERIAL;
+		material_shadow = (SceneShaderForwardMobile::MaterialData *)storage->material_get_data(scene_shader.default_material, RendererStorageRD::SHADER_TYPE_3D);
+
+		RID shadow_mesh = storage->mesh_get_shadow_mesh(p_mesh);
+
+		if (shadow_mesh.is_valid()) {
+			surface_shadow = storage->mesh_get_surface(shadow_mesh, p_surface);
+		}
+
+	} else {
+		material_shadow = p_material;
+	}
+
+	GeometryInstanceSurfaceDataCache *sdcache = geometry_instance_surface_alloc.alloc();
+
+	sdcache->flags = flags;
+
+	sdcache->shader = p_material->shader_data;
+	sdcache->material_uniform_set = p_material->uniform_set;
+	sdcache->surface = storage->mesh_get_surface(p_mesh, p_surface);
+	sdcache->primitive = storage->mesh_surface_get_primitive(sdcache->surface);
+	sdcache->surface_index = p_surface;
+
+	if (ginstance->data->dirty_dependencies) {
+		storage->base_update_dependency(p_mesh, &ginstance->data->dependency_tracker);
+	}
+
+	//shadow
+	sdcache->shader_shadow = material_shadow->shader_data;
+	sdcache->material_uniform_set_shadow = material_shadow->uniform_set;
+
+	sdcache->surface_shadow = surface_shadow ? surface_shadow : sdcache->surface;
+
+	sdcache->owner = ginstance;
+
+	sdcache->next = ginstance->surface_caches;
+	ginstance->surface_caches = sdcache;
+
+	//sortkey
+
+	sdcache->sort.sort_key1 = 0;
+	sdcache->sort.sort_key2 = 0;
+
+	sdcache->sort.surface_index = p_surface;
+	sdcache->sort.material_id_low = p_material_id & 0x0000FFFF;
+	sdcache->sort.material_id_hi = p_material_id >> 16;
+	sdcache->sort.shader_id = p_shader_id;
+	sdcache->sort.geometry_id = p_mesh.get_local_index(); //only meshes can repeat anyway
+	// sdcache->sort.uses_forward_gi = ginstance->can_sdfgi;
+	sdcache->sort.priority = p_material->priority;
+}
+
+void RenderForwardMobile::_geometry_instance_add_surface(GeometryInstanceForwardMobile *ginstance, uint32_t p_surface, RID p_material, RID p_mesh) {
+	RID m_src;
+
+	m_src = ginstance->data->material_override.is_valid() ? ginstance->data->material_override : p_material;
+
+	SceneShaderForwardMobile::MaterialData *material = nullptr;
+
+	if (m_src.is_valid()) {
+		material = (SceneShaderForwardMobile::MaterialData *)storage->material_get_data(m_src, RendererStorageRD::SHADER_TYPE_3D);
+		if (!material || !material->shader_data->valid) {
+			material = nullptr;
+		}
+	}
+
+	if (material) {
+		if (ginstance->data->dirty_dependencies) {
+			storage->material_update_dependency(m_src, &ginstance->data->dependency_tracker);
+		}
+	} else {
+		material = (SceneShaderForwardMobile::MaterialData *)storage->material_get_data(scene_shader.default_material, RendererStorageRD::SHADER_TYPE_3D);
+		m_src = scene_shader.default_material;
+	}
+
+	ERR_FAIL_COND(!material);
+
+	_geometry_instance_add_surface_with_material(ginstance, p_surface, material, m_src.get_local_index(), storage->material_get_shader_id(m_src), p_mesh);
+
+	while (material->next_pass.is_valid()) {
+		RID next_pass = material->next_pass;
+		material = (SceneShaderForwardMobile::MaterialData *)storage->material_get_data(next_pass, RendererStorageRD::SHADER_TYPE_3D);
+		if (!material || !material->shader_data->valid) {
+			break;
+		}
+		if (ginstance->data->dirty_dependencies) {
+			storage->material_update_dependency(next_pass, &ginstance->data->dependency_tracker);
+		}
+		_geometry_instance_add_surface_with_material(ginstance, p_surface, material, next_pass.get_local_index(), storage->material_get_shader_id(next_pass), p_mesh);
+	}
+}
+
+void RenderForwardMobile::_geometry_instance_update(GeometryInstance *p_geometry_instance) {
+	GeometryInstanceForwardMobile *ginstance = static_cast<GeometryInstanceForwardMobile *>(p_geometry_instance);
+
+	if (ginstance->data->dirty_dependencies) {
+		ginstance->data->dependency_tracker.update_begin();
+	}
+
+	//add geometry for drawing
+	switch (ginstance->data->base_type) {
+		case RS::INSTANCE_MESH: {
+			const RID *materials = nullptr;
+			uint32_t surface_count;
+			RID mesh = ginstance->data->base;
+
+			materials = storage->mesh_get_surface_count_and_materials(mesh, surface_count);
+			if (materials) {
+				//if no materials, no surfaces.
+				const RID *inst_materials = ginstance->data->surface_materials.ptr();
+				uint32_t surf_mat_count = ginstance->data->surface_materials.size();
+
+				for (uint32_t j = 0; j < surface_count; j++) {
+					RID material = (j < surf_mat_count && inst_materials[j].is_valid()) ? inst_materials[j] : materials[j];
+					_geometry_instance_add_surface(ginstance, j, material, mesh);
+				}
+			}
+
+			ginstance->instance_count = 1;
+
+		} break;
+
+		case RS::INSTANCE_MULTIMESH: {
+			RID mesh = storage->multimesh_get_mesh(ginstance->data->base);
+			if (mesh.is_valid()) {
+				const RID *materials = nullptr;
+				uint32_t surface_count;
+
+				materials = storage->mesh_get_surface_count_and_materials(mesh, surface_count);
+				if (materials) {
+					for (uint32_t j = 0; j < surface_count; j++) {
+						_geometry_instance_add_surface(ginstance, j, materials[j], mesh);
+					}
+				}
+
+				ginstance->instance_count = storage->multimesh_get_instances_to_draw(ginstance->data->base);
+			}
+
+		} break;
+#if 0
+		case RS::INSTANCE_IMMEDIATE: {
+			RasterizerStorageGLES3::Immediate *immediate = storage->immediate_owner.getornull(inst->base);
+			ERR_CONTINUE(!immediate);
+
+			_add_geometry(immediate, inst, nullptr, -1, p_depth_pass, p_shadow_pass);
+
+		} break;
+#endif
+		case RS::INSTANCE_PARTICLES: {
+			int draw_passes = storage->particles_get_draw_passes(ginstance->data->base);
+
+			for (int j = 0; j < draw_passes; j++) {
+				RID mesh = storage->particles_get_draw_pass_mesh(ginstance->data->base, j);
+				if (!mesh.is_valid()) {
+					continue;
+				}
+
+				const RID *materials = nullptr;
+				uint32_t surface_count;
+
+				materials = storage->mesh_get_surface_count_and_materials(mesh, surface_count);
+				if (materials) {
+					for (uint32_t k = 0; k < surface_count; k++) {
+						_geometry_instance_add_surface(ginstance, k, materials[k], mesh);
+					}
+				}
+			}
+
+			ginstance->instance_count = storage->particles_get_amount(ginstance->data->base, ginstance->trail_steps);
+
+		} break;
+
+		default: {
+		}
+	}
+
+	//Fill push constant
+
+	bool store_transform = true;
+
+	if (ginstance->data->base_type == RS::INSTANCE_MULTIMESH) {
+		ginstance->base_flags |= INSTANCE_DATA_FLAG_MULTIMESH;
+		if (storage->multimesh_get_transform_format(ginstance->data->base) == RS::MULTIMESH_TRANSFORM_2D) {
+			ginstance->base_flags |= INSTANCE_DATA_FLAG_MULTIMESH_FORMAT_2D;
+		}
+		if (storage->multimesh_uses_colors(ginstance->data->base)) {
+			ginstance->base_flags |= INSTANCE_DATA_FLAG_MULTIMESH_HAS_COLOR;
+		}
+		if (storage->multimesh_uses_custom_data(ginstance->data->base)) {
+			ginstance->base_flags |= INSTANCE_DATA_FLAG_MULTIMESH_HAS_CUSTOM_DATA;
+		}
+
+		ginstance->transforms_uniform_set = storage->multimesh_get_3d_uniform_set(ginstance->data->base, scene_shader.default_shader_rd, TRANSFORMS_UNIFORM_SET);
+
+	} else if (ginstance->data->base_type == RS::INSTANCE_PARTICLES) {
+		ginstance->base_flags |= INSTANCE_DATA_FLAG_MULTIMESH;
+		if (false) { // 2D particles
+			ginstance->base_flags |= INSTANCE_DATA_FLAG_MULTIMESH_FORMAT_2D;
+		}
+
+		ginstance->base_flags |= INSTANCE_DATA_FLAG_MULTIMESH_HAS_COLOR;
+		ginstance->base_flags |= INSTANCE_DATA_FLAG_MULTIMESH_HAS_CUSTOM_DATA;
+
+		//for particles, stride is the trail size
+		ginstance->base_flags |= (ginstance->trail_steps << INSTANCE_DATA_FLAGS_PARTICLE_TRAIL_SHIFT);
+
+		if (!storage->particles_is_using_local_coords(ginstance->data->base)) {
+			store_transform = false;
+		}
+		ginstance->transforms_uniform_set = storage->particles_get_instance_buffer_uniform_set(ginstance->data->base, scene_shader.default_shader_rd, TRANSFORMS_UNIFORM_SET);
+
+	} else if (ginstance->data->base_type == RS::INSTANCE_MESH) {
+		if (storage->skeleton_is_valid(ginstance->data->skeleton)) {
+			ginstance->transforms_uniform_set = storage->skeleton_get_3d_uniform_set(ginstance->data->skeleton, scene_shader.default_shader_rd, TRANSFORMS_UNIFORM_SET);
+			if (ginstance->data->dirty_dependencies) {
+				storage->skeleton_update_dependency(ginstance->data->skeleton, &ginstance->data->dependency_tracker);
+			}
+		}
+	}
+
+	ginstance->store_transform_cache = store_transform;
+
+	if (ginstance->data->dirty_dependencies) {
+		ginstance->data->dependency_tracker.update_end();
+		ginstance->data->dirty_dependencies = false;
+	}
+
+	ginstance->dirty_list_element.remove_from_list();
+}
+
+void RenderForwardMobile::_update_dirty_geometry_instances() {
+	while (geometry_instance_dirty_list.first()) {
+		_geometry_instance_update(geometry_instance_dirty_list.first()->self());
+	}
+}
+
+void RenderForwardMobile::_geometry_instance_dependency_changed(RendererStorage::DependencyChangedNotification p_notification, RendererStorage::DependencyTracker *p_tracker) {
+	switch (p_notification) {
+		case RendererStorage::DEPENDENCY_CHANGED_MATERIAL:
+		case RendererStorage::DEPENDENCY_CHANGED_MESH:
+		case RendererStorage::DEPENDENCY_CHANGED_PARTICLES:
+		case RendererStorage::DEPENDENCY_CHANGED_MULTIMESH:
+		case RendererStorage::DEPENDENCY_CHANGED_SKELETON_DATA: {
+			static_cast<RenderForwardMobile *>(singleton)->_geometry_instance_mark_dirty(static_cast<GeometryInstance *>(p_tracker->userdata));
+		} break;
+		case RendererStorage::DEPENDENCY_CHANGED_MULTIMESH_VISIBLE_INSTANCES: {
+			GeometryInstanceForwardMobile *ginstance = static_cast<GeometryInstanceForwardMobile *>(p_tracker->userdata);
+			if (ginstance->data->base_type == RS::INSTANCE_MULTIMESH) {
+				ginstance->instance_count = static_cast<RenderForwardMobile *>(singleton)->storage->multimesh_get_instances_to_draw(ginstance->data->base);
+			}
+		} break;
+		default: {
+			//rest of notifications of no interest
+		} break;
+	}
+}
+void RenderForwardMobile::_geometry_instance_dependency_deleted(const RID &p_dependency, RendererStorage::DependencyTracker *p_tracker) {
+	static_cast<RenderForwardMobile *>(singleton)->_geometry_instance_mark_dirty(static_cast<GeometryInstance *>(p_tracker->userdata));
+}
+
+/* misc */
+
+bool RenderForwardMobile::is_dynamic_gi_supported() const {
+	return false;
+}
+
+bool RenderForwardMobile::is_clustered_enabled() const {
+	return false;
+}
+
+bool RenderForwardMobile::is_volumetric_supported() const {
+	return false;
+}
+
+uint32_t RenderForwardMobile::get_max_elements() const {
+	return 256;
+}
+
+RenderForwardMobile *RenderForwardMobile::singleton = nullptr;
+
+RenderForwardMobile::RenderForwardMobile(RendererStorageRD *p_storage) :
+		RendererSceneRenderRD(p_storage) {
+	singleton = this;
+
+	String defines;
+
+	defines += "\n#define MAX_ROUGHNESS_LOD " + itos(get_roughness_layers() - 1) + ".0\n";
+	if (is_using_radiance_cubemap_array()) {
+		defines += "\n#define USE_RADIANCE_CUBEMAP_ARRAY \n";
+	}
+	// defines += "\n#define SDFGI_OCT_SIZE " + itos(gi.sdfgi_get_lightprobe_octahedron_size()) + "\n";
+	defines += "\n#define MAX_DIRECTIONAL_LIGHT_DATA_STRUCTS " + itos(get_max_directional_lights()) + "\n";
+
+	{
+		//lightmaps
+		scene_state.max_lightmaps = 2;
+		defines += "\n#define MAX_LIGHTMAP_TEXTURES " + itos(scene_state.max_lightmaps) + "\n";
+		defines += "\n#define MAX_LIGHTMAPS " + itos(scene_state.max_lightmaps) + "\n";
+
+		scene_state.lightmap_buffer = RD::get_singleton()->storage_buffer_create(sizeof(LightmapData) * scene_state.max_lightmaps);
+	}
+	{
+		//captures
+		scene_state.max_lightmap_captures = 2048;
+		scene_state.lightmap_captures = memnew_arr(LightmapCaptureData, scene_state.max_lightmap_captures);
+		scene_state.lightmap_capture_buffer = RD::get_singleton()->storage_buffer_create(sizeof(LightmapCaptureData) * scene_state.max_lightmap_captures);
+	}
+	{
+		defines += "\n#define MATERIAL_UNIFORM_SET " + itos(MATERIAL_UNIFORM_SET) + "\n";
+	}
+
+	scene_shader.init(p_storage, defines);
+
+	// !BAS! maybe we need a mobile version of this setting?
+	render_list_thread_threshold = GLOBAL_GET("rendering/limits/forward_renderer/threaded_render_minimum_instances");
+}
+
+RenderForwardMobile::~RenderForwardMobile() {
+	directional_shadow_atlas_set_size(0);
+
+	//clear base uniform set if still valid
+	for (uint32_t i = 0; i < render_pass_uniform_sets.size(); i++) {
+		if (render_pass_uniform_sets[i].is_valid() && RD::get_singleton()->uniform_set_is_valid(render_pass_uniform_sets[i])) {
+			RD::get_singleton()->free(render_pass_uniform_sets[i]);
+		}
+	}
+
+	{
+		for (uint32_t i = 0; i < scene_state.uniform_buffers.size(); i++) {
+			RD::get_singleton()->free(scene_state.uniform_buffers[i]);
+		}
+		RD::get_singleton()->free(scene_state.lightmap_buffer);
+		RD::get_singleton()->free(scene_state.lightmap_capture_buffer);
+		memdelete_arr(scene_state.lightmap_captures);
+	}
+}

+ 604 - 0
servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h

@@ -0,0 +1,604 @@
+/*************************************************************************/
+/*  render_forward_mobile.h                                              */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md).   */
+/*                                                                       */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the       */
+/* "Software"), to deal in the Software without restriction, including   */
+/* without limitation the rights to use, copy, modify, merge, publish,   */
+/* distribute, sublicense, and/or sell copies of the Software, and to    */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions:                                             */
+/*                                                                       */
+/* The above copyright notice and this permission notice shall be        */
+/* included in all copies or substantial portions of the Software.       */
+/*                                                                       */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
+/*************************************************************************/
+
+#ifndef RENDERING_SERVER_SCENE_RENDER_FORWARD_MOBILE_H
+#define RENDERING_SERVER_SCENE_RENDER_FORWARD_MOBILE_H
+
+#include "core/templates/paged_allocator.h"
+#include "servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.h"
+#include "servers/rendering/renderer_rd/pipeline_cache_rd.h"
+#include "servers/rendering/renderer_rd/renderer_scene_render_rd.h"
+#include "servers/rendering/renderer_rd/renderer_storage_rd.h"
+
+namespace RendererSceneRenderImplementation {
+
+class RenderForwardMobile : public RendererSceneRenderRD {
+	friend SceneShaderForwardMobile;
+
+protected:
+	/* Scene Shader */
+
+	enum {
+		SCENE_UNIFORM_SET = 0,
+		RENDER_PASS_UNIFORM_SET = 1,
+		TRANSFORMS_UNIFORM_SET = 2,
+		MATERIAL_UNIFORM_SET = 3
+	};
+
+	enum {
+		MAX_LIGHTMAPS = 8,
+		MAX_RDL_CULL = 8, // maximum number of reflection probes, decals or lights we can cull per geometry instance
+		INSTANCE_DATA_BUFFER_MIN_SIZE = 4096
+	};
+
+	enum RenderListType {
+		RENDER_LIST_OPAQUE, //used for opaque objects
+		RENDER_LIST_ALPHA, //used for transparent objects
+		RENDER_LIST_SECONDARY, //used for shadows and other objects
+		RENDER_LIST_MAX
+	};
+
+	/* Scene Shader */
+
+	SceneShaderForwardMobile scene_shader;
+
+	/* Render Buffer */
+
+	struct RenderBufferDataForwardMobile : public RenderBufferData {
+		RID color;
+		RID depth;
+		// RID normal_roughness_buffer;
+
+		RS::ViewportMSAA msaa;
+		RD::TextureSamples texture_samples;
+
+		RID color_msaa;
+		RID depth_msaa;
+		// RID normal_roughness_buffer_msaa;
+
+		RID color_fb;
+		int width, height;
+
+		void clear();
+		virtual void configure(RID p_color_buffer, RID p_depth_buffer, int p_width, int p_height, RS::ViewportMSAA p_msaa);
+
+		~RenderBufferDataForwardMobile();
+	};
+
+	virtual RenderBufferData *_create_render_buffer_data();
+
+	/* Rendering */
+
+	enum PassMode {
+		PASS_MODE_COLOR,
+		// PASS_MODE_COLOR_SPECULAR,
+		PASS_MODE_COLOR_TRANSPARENT,
+		PASS_MODE_SHADOW,
+		PASS_MODE_SHADOW_DP,
+		// PASS_MODE_DEPTH,
+		// PASS_MODE_DEPTH_NORMAL_ROUGHNESS,
+		// PASS_MODE_DEPTH_NORMAL_ROUGHNESS_GIPROBE,
+		PASS_MODE_DEPTH_MATERIAL,
+		// PASS_MODE_SDF,
+	};
+
+	struct GeometryInstanceForwardMobile;
+	struct GeometryInstanceSurfaceDataCache;
+	struct RenderElementInfo;
+
+	struct RenderListParameters {
+		GeometryInstanceSurfaceDataCache **elements = nullptr;
+		RenderElementInfo *element_info = nullptr;
+		int element_count = 0;
+		bool reverse_cull = false;
+		PassMode pass_mode = PASS_MODE_COLOR;
+		// bool no_gi = false;
+		RID render_pass_uniform_set;
+		bool force_wireframe = false;
+		Vector2 uv_offset;
+		Plane lod_plane;
+		float lod_distance_multiplier = 0.0;
+		float screen_lod_threshold = 0.0;
+		RD::FramebufferFormatID framebuffer_format = 0;
+		uint32_t element_offset = 0;
+		uint32_t barrier = RD::BARRIER_MASK_ALL;
+
+		RenderListParameters(GeometryInstanceSurfaceDataCache **p_elements, RenderElementInfo *p_element_info, int p_element_count, bool p_reverse_cull, PassMode p_pass_mode, RID p_render_pass_uniform_set, bool p_force_wireframe = false, const Vector2 &p_uv_offset = Vector2(), const Plane &p_lod_plane = Plane(), float p_lod_distance_multiplier = 0.0, float p_screen_lod_threshold = 0.0, uint32_t p_element_offset = 0, uint32_t p_barrier = RD::BARRIER_MASK_ALL) {
+			elements = p_elements;
+			element_info = p_element_info;
+			element_count = p_element_count;
+			reverse_cull = p_reverse_cull;
+			pass_mode = p_pass_mode;
+			// no_gi = p_no_gi;
+			render_pass_uniform_set = p_render_pass_uniform_set;
+			force_wireframe = p_force_wireframe;
+			uv_offset = p_uv_offset;
+			lod_plane = p_lod_plane;
+			lod_distance_multiplier = p_lod_distance_multiplier;
+			screen_lod_threshold = p_screen_lod_threshold;
+			element_offset = p_element_offset;
+			barrier = p_barrier;
+		}
+	};
+
+	RID _setup_render_pass_uniform_set(RenderListType p_render_list, RID p_render_buffers, RID p_radiance_texture, RID p_shadow_atlas, RID p_reflection_atlas, const PagedArray<RID> &p_lightmaps, bool p_use_directional_shadow_atlas = false, int p_index = 0);
+	virtual void _render_scene(RID p_render_buffer, const Transform &p_cam_transform, const CameraMatrix &p_cam_projection, bool p_cam_ortogonal, const PagedArray<GeometryInstance *> &p_instances, const PagedArray<RID> &p_gi_probes, const PagedArray<RID> &p_lightmaps, RID p_environment, RID p_cluster_buffer, uint32_t p_cluster_size, uint32_t p_cluster_max_elements, RID p_camera_effects, RID p_shadow_atlas, RID p_reflection_atlas, RID p_reflection_probe, int p_reflection_probe_pass, const Color &p_default_bg_color, float p_screen_lod_threshold);
+
+	virtual void _render_shadow_begin();
+	virtual void _render_shadow_append(RID p_framebuffer, const PagedArray<GeometryInstance *> &p_instances, const CameraMatrix &p_projection, const Transform &p_transform, float p_zfar, float p_bias, float p_normal_bias, bool p_use_dp, bool p_use_dp_flip, bool p_use_pancake, const Plane &p_camera_plane = Plane(), float p_lod_distance_multiplier = 0.0, float p_screen_lod_threshold = 0.0, const Rect2i &p_rect = Rect2i(), bool p_flip_y = false, bool p_clear_region = true, bool p_begin = true, bool p_end = true);
+	virtual void _render_shadow_process();
+	virtual void _render_shadow_end(uint32_t p_barrier = RD::BARRIER_MASK_ALL);
+
+	virtual void _render_material(const Transform &p_cam_transform, const CameraMatrix &p_cam_projection, bool p_cam_ortogonal, const PagedArray<GeometryInstance *> &p_instances, RID p_framebuffer, const Rect2i &p_region);
+	virtual void _render_uv2(const PagedArray<GeometryInstance *> &p_instances, RID p_framebuffer, const Rect2i &p_region);
+	virtual void _render_sdfgi(RID p_render_buffers, const Vector3i &p_from, const Vector3i &p_size, const AABB &p_bounds, const PagedArray<GeometryInstance *> &p_instances, const RID &p_albedo_texture, const RID &p_emission_texture, const RID &p_emission_aniso_texture, const RID &p_geom_facing_texture);
+	virtual void _render_particle_collider_heightfield(RID p_fb, const Transform &p_cam_transform, const CameraMatrix &p_cam_projection, const PagedArray<GeometryInstance *> &p_instances);
+
+	uint64_t lightmap_texture_array_version = 0xFFFFFFFF;
+
+	virtual void _base_uniforms_changed();
+	void _update_render_base_uniform_set();
+	virtual RID _render_buffers_get_normal_texture(RID p_render_buffers);
+
+	void _fill_render_list(RenderListType p_render_list, const PagedArray<GeometryInstance *> &p_instances, PassMode p_pass_mode, const CameraMatrix &p_cam_projection, const Transform &p_cam_transform, const Plane &p_lod_camera_plane = Plane(), float p_lod_distance_multiplier = 0.0, float p_screen_lod_threshold = 0.0, bool p_append = false);
+	void _fill_instance_data(RenderListType p_render_list, uint32_t p_offset = 0, int32_t p_max_elements = -1, bool p_update_buffer = true);
+	// void _update_instance_data_buffer(RenderListType p_render_list);
+
+	static RenderForwardMobile *singleton;
+
+	void _setup_environment(RID p_environment, RID p_render_buffers, const CameraMatrix &p_cam_projection, const Transform &p_cam_transform, RID p_reflection_probe, bool p_no_fog, const Size2i &p_screen_size, RID p_shadow_atlas, bool p_flip_y, const Color &p_default_bg_color, float p_znear, float p_zfar, bool p_opaque_render_buffers = false, bool p_pancake_shadows = false, int p_index = 0);
+	void _setup_lightmaps(const PagedArray<RID> &p_lightmaps, const Transform &p_cam_transform);
+
+	RID render_base_uniform_set;
+	LocalVector<RID> render_pass_uniform_sets;
+
+	/* Light map */
+
+	struct LightmapData {
+		float normal_xform[12];
+	};
+
+	struct LightmapCaptureData {
+		float sh[9 * 4];
+	};
+
+	/* Scene state */
+
+	struct SceneState {
+		// This struct is loaded into Set 1 - Binding 0, populated at start of rendering a frame, must match with shader code
+		struct UBO {
+			float projection_matrix[16];
+			float inv_projection_matrix[16];
+
+			float camera_matrix[16];
+			float inv_camera_matrix[16];
+
+			float viewport_size[2];
+			float screen_pixel_size[2];
+
+			float directional_penumbra_shadow_kernel[128]; //32 vec4s
+			float directional_soft_shadow_kernel[128];
+			float penumbra_shadow_kernel[128];
+			float soft_shadow_kernel[128];
+
+			uint32_t directional_penumbra_shadow_samples;
+			uint32_t directional_soft_shadow_samples;
+			uint32_t penumbra_shadow_samples;
+			uint32_t soft_shadow_samples;
+
+			float ambient_light_color_energy[4];
+
+			float ambient_color_sky_mix;
+			uint32_t use_ambient_light;
+			uint32_t use_ambient_cubemap;
+			uint32_t use_reflection_cubemap;
+
+			float radiance_inverse_xform[12];
+
+			float shadow_atlas_pixel_size[2];
+			float directional_shadow_pixel_size[2];
+
+			uint32_t directional_light_count;
+			float dual_paraboloid_side;
+			float z_far;
+			float z_near;
+
+			uint32_t ssao_enabled;
+			float ssao_light_affect;
+			float ssao_ao_affect;
+			uint32_t roughness_limiter_enabled;
+
+			float roughness_limiter_amount;
+			float roughness_limiter_limit;
+			uint32_t roughness_limiter_pad[2];
+
+			float ao_color[4];
+
+			// Fog
+			uint32_t fog_enabled;
+			float fog_density;
+			float fog_height;
+			float fog_height_density;
+
+			float fog_light_color[3];
+			float fog_sun_scatter;
+
+			float fog_aerial_perspective;
+			uint32_t material_uv2_mode;
+
+			float time;
+			float reflection_multiplier;
+
+			uint32_t pancake_shadows;
+			uint32_t pad1;
+			uint32_t pad2;
+			uint32_t pad3;
+		};
+
+		UBO ubo;
+
+		LocalVector<RID> uniform_buffers;
+
+		// !BAS! We need to change lightmaps, we're not going to do this with a buffer but pushing the used lightmap in
+		LightmapData lightmaps[MAX_LIGHTMAPS];
+		RID lightmap_ids[MAX_LIGHTMAPS];
+		bool lightmap_has_sh[MAX_LIGHTMAPS];
+		uint32_t lightmaps_used = 0;
+		uint32_t max_lightmaps;
+		RID lightmap_buffer;
+
+		LightmapCaptureData *lightmap_captures;
+		uint32_t max_lightmap_captures;
+		RID lightmap_capture_buffer;
+
+		bool used_screen_texture = false;
+		bool used_normal_texture = false;
+		bool used_depth_texture = false;
+		bool used_sss = false;
+
+		struct ShadowPass {
+			uint32_t element_from;
+			uint32_t element_count;
+			bool flip_cull;
+			PassMode pass_mode;
+
+			RID rp_uniform_set;
+			Plane camera_plane;
+			float lod_distance_multiplier;
+			float screen_lod_threshold;
+
+			RID framebuffer;
+			RD::InitialAction initial_depth_action;
+			RD::FinalAction final_depth_action;
+			Rect2i rect;
+		};
+
+		LocalVector<ShadowPass> shadow_passes;
+	} scene_state;
+
+	/* Render List */
+
+	// !BAS! Render list can probably be reused between clustered and mobile?
+	struct RenderList {
+		LocalVector<GeometryInstanceSurfaceDataCache *> elements;
+		LocalVector<RenderElementInfo> element_info;
+
+		void clear() {
+			elements.clear();
+			element_info.clear();
+		}
+
+		//should eventually be replaced by radix
+
+		struct SortByKey {
+			_FORCE_INLINE_ bool operator()(const GeometryInstanceSurfaceDataCache *A, const GeometryInstanceSurfaceDataCache *B) const {
+				return (A->sort.sort_key2 == B->sort.sort_key2) ? (A->sort.sort_key1 < B->sort.sort_key1) : (A->sort.sort_key2 < B->sort.sort_key2);
+			}
+		};
+
+		void sort_by_key() {
+			SortArray<GeometryInstanceSurfaceDataCache *, SortByKey> sorter;
+			sorter.sort(elements.ptr(), elements.size());
+		}
+
+		void sort_by_key_range(uint32_t p_from, uint32_t p_size) {
+			SortArray<GeometryInstanceSurfaceDataCache *, SortByKey> sorter;
+			sorter.sort(elements.ptr() + p_from, p_size);
+		}
+
+		struct SortByDepth {
+			_FORCE_INLINE_ bool operator()(const GeometryInstanceSurfaceDataCache *A, const GeometryInstanceSurfaceDataCache *B) const {
+				return (A->owner->depth < B->owner->depth);
+			}
+		};
+
+		void sort_by_depth() { //used for shadows
+
+			SortArray<GeometryInstanceSurfaceDataCache *, SortByDepth> sorter;
+			sorter.sort(elements.ptr(), elements.size());
+		}
+
+		struct SortByReverseDepthAndPriority {
+			_FORCE_INLINE_ bool operator()(const GeometryInstanceSurfaceDataCache *A, const GeometryInstanceSurfaceDataCache *B) const {
+				return (A->sort.priority == B->sort.priority) ? (A->owner->depth > B->owner->depth) : (A->sort.priority < B->sort.priority);
+			}
+		};
+
+		void sort_by_reverse_depth_and_priority(bool p_alpha) { //used for alpha
+
+			SortArray<GeometryInstanceSurfaceDataCache *, SortByReverseDepthAndPriority> sorter;
+			sorter.sort(elements.ptr(), elements.size());
+		}
+
+		_FORCE_INLINE_ void add_element(GeometryInstanceSurfaceDataCache *p_element) {
+			elements.push_back(p_element);
+		}
+	};
+
+	struct RenderElementInfo {
+		uint32_t repeat : 22;
+		uint32_t uses_lightmap : 1;
+		uint32_t lod_index : 8;
+		uint32_t reserved : 1; // was uses_forward_gi but we don't use that here
+	};
+
+	template <PassMode p_pass_mode>
+	_FORCE_INLINE_ void _render_list_template(RenderingDevice::DrawListID p_draw_list, RenderingDevice::FramebufferFormatID p_framebuffer_Format, RenderListParameters *p_params, uint32_t p_from_element, uint32_t p_to_element);
+
+	void _render_list(RenderingDevice::DrawListID p_draw_list, RenderingDevice::FramebufferFormatID p_framebuffer_Format, RenderListParameters *p_params, uint32_t p_from_element, uint32_t p_to_element);
+
+	LocalVector<RD::DrawListID> thread_draw_lists;
+	void _render_list_thread_function(uint32_t p_thread, RenderListParameters *p_params);
+	void _render_list_with_threads(RenderListParameters *p_params, RID p_framebuffer, RD::InitialAction p_initial_color_action, RD::FinalAction p_final_color_action, RD::InitialAction p_initial_depth_action, RD::FinalAction p_final_depth_action, const Vector<Color> &p_clear_color_values = Vector<Color>(), float p_clear_depth = 1.0, uint32_t p_clear_stencil = 0, const Rect2 &p_region = Rect2(), const Vector<RID> &p_storage_textures = Vector<RID>());
+
+	uint32_t render_list_thread_threshold = 500;
+
+	RenderList render_list[RENDER_LIST_MAX];
+
+	/* Geometry instance */
+
+	// check which ones of these apply, probably all except GI and SDFGI
+	enum {
+		INSTANCE_DATA_FLAG_USE_GI_BUFFERS = 1 << 6,
+		INSTANCE_DATA_FLAG_USE_SDFGI = 1 << 7,
+		INSTANCE_DATA_FLAG_USE_LIGHTMAP_CAPTURE = 1 << 8,
+		INSTANCE_DATA_FLAG_USE_LIGHTMAP = 1 << 9,
+		INSTANCE_DATA_FLAG_USE_SH_LIGHTMAP = 1 << 10,
+		INSTANCE_DATA_FLAG_USE_GIPROBE = 1 << 11,
+		INSTANCE_DATA_FLAG_MULTIMESH = 1 << 12,
+		INSTANCE_DATA_FLAG_MULTIMESH_FORMAT_2D = 1 << 13,
+		INSTANCE_DATA_FLAG_MULTIMESH_HAS_COLOR = 1 << 14,
+		INSTANCE_DATA_FLAG_MULTIMESH_HAS_CUSTOM_DATA = 1 << 15,
+		INSTANCE_DATA_FLAGS_PARTICLE_TRAIL_SHIFT = 16,
+		INSTANCE_DATA_FLAGS_PARTICLE_TRAIL_MASK = 0xFF,
+		INSTANCE_DATA_FLAGS_NON_UNIFORM_SCALE = 1 << 24,
+	};
+
+	struct GeometryInstanceLightmapSH {
+		Color sh[9];
+	};
+
+	// Cached data for drawing surfaces
+	struct GeometryInstanceSurfaceDataCache {
+		enum {
+			FLAG_PASS_DEPTH = 1,
+			FLAG_PASS_OPAQUE = 2,
+			FLAG_PASS_ALPHA = 4,
+			FLAG_PASS_SHADOW = 8,
+			FLAG_USES_SHARED_SHADOW_MATERIAL = 128,
+			FLAG_USES_SUBSURFACE_SCATTERING = 2048,
+			FLAG_USES_SCREEN_TEXTURE = 4096,
+			FLAG_USES_DEPTH_TEXTURE = 8192,
+			FLAG_USES_NORMAL_TEXTURE = 16384,
+			FLAG_USES_DOUBLE_SIDED_SHADOWS = 32768,
+			FLAG_USES_PARTICLE_TRAILS = 65536,
+		};
+
+		union {
+			struct {
+				// !BAS! CHECK BITS!!!
+
+				uint64_t surface_index : 10;
+				uint64_t geometry_id : 32;
+				uint64_t material_id_low : 16;
+
+				uint64_t material_id_hi : 16;
+				uint64_t shader_id : 32;
+				uint64_t uses_lightmap : 4; // sort by lightmap id here, not whether its yes/no (is 4 bits enough?)
+				uint64_t depth_layer : 4;
+				uint64_t priority : 8;
+
+				// uint64_t lod_index : 8; // no need to sort on LOD
+				// uint64_t uses_forward_gi : 1; // no GI here, remove
+			};
+			struct {
+				uint64_t sort_key1;
+				uint64_t sort_key2;
+			};
+		} sort;
+
+		RS::PrimitiveType primitive = RS::PRIMITIVE_MAX;
+		uint32_t flags = 0;
+		uint32_t surface_index = 0;
+		uint32_t lod_index = 0;
+
+		void *surface = nullptr;
+		RID material_uniform_set;
+		SceneShaderForwardMobile::ShaderData *shader = nullptr;
+
+		void *surface_shadow = nullptr;
+		RID material_uniform_set_shadow;
+		SceneShaderForwardMobile::ShaderData *shader_shadow = nullptr;
+
+		GeometryInstanceSurfaceDataCache *next = nullptr;
+		GeometryInstanceForwardMobile *owner = nullptr;
+	};
+
+	// !BAS! GeometryInstanceForwardClustered and GeometryInstanceForwardMobile will likely have a lot of overlap
+	// may need to think about making this its own class like GeometryInstanceRD?
+
+	struct GeometryInstanceForwardMobile : public GeometryInstance {
+		// setup
+		uint32_t base_flags = 0;
+		uint32_t flags_cache = 0;
+
+		// this structure maps to our push constant in our shader and is populated right before our draw call
+		struct PushConstant {
+			float transform[16];
+			uint32_t flags;
+			uint32_t instance_uniforms_ofs; //base offset in global buffer for instance variables
+			uint32_t gi_offset; //GI information when using lightmapping (VCT or lightmap index)
+			uint32_t layer_mask = 1;
+			float lightmap_uv_scale[4]; // doubles as uv_offset when needed
+			uint32_t reflection_probes[2]; // packed reflection probes
+			uint32_t omni_lights[2]; // packed omni lights
+			uint32_t spot_lights[2]; // packed spot lights
+			uint32_t decals[2]; // packed spot lights
+		};
+
+		// PushConstant push_constant; // we populate this from our instance data
+
+		//used during rendering
+		uint32_t layer_mask = 1;
+		RID transforms_uniform_set;
+		float depth = 0;
+		bool mirror = false;
+		Transform transform;
+		bool store_transform_cache = true; // if true we copy our transform into our PushConstant, if false we use our transforms UBO and clear our PushConstants transform
+		bool non_uniform_scale = false;
+		AABB transformed_aabb; //needed for LOD
+		float lod_bias = 0.0;
+		float lod_model_scale = 1.0;
+		int32_t shader_parameters_offset = -1;
+		uint32_t instance_count = 0;
+		uint32_t trail_steps = 1;
+		RID mesh_instance;
+
+		// lightmap
+		uint32_t gi_offset_cache = 0; // !BAS! Should rename this to lightmap_offset_cache, in forward clustered this was shared between gi and lightmap
+		uint32_t lightmap_slice_index;
+		Rect2 lightmap_uv_scale;
+		RID lightmap_instance;
+		GeometryInstanceLightmapSH *lightmap_sh = nullptr;
+
+		// culled light info
+		uint32_t reflection_probe_count;
+		RID reflection_probes[MAX_RDL_CULL];
+		uint32_t omni_light_count;
+		RID omni_lights[MAX_RDL_CULL];
+		uint32_t spot_light_count;
+		RID spot_lights[MAX_RDL_CULL];
+		uint32_t decals_count;
+		RID decals[MAX_RDL_CULL];
+
+		GeometryInstanceSurfaceDataCache *surface_caches = nullptr;
+
+		// do we use this?
+		SelfList<GeometryInstanceForwardMobile> dirty_list_element;
+
+		struct Data {
+			//data used less often goes into regular heap
+			RID base;
+			RS::InstanceType base_type;
+
+			RID skeleton;
+			Vector<RID> surface_materials;
+			RID material_override;
+			AABB aabb;
+
+			bool use_baked_light = false;
+			bool cast_double_sided_shadows = false;
+			// bool mirror = false; // !BAS! Does not seem used, we already have this in the main struct
+
+			bool dirty_dependencies = false;
+
+			RendererStorage::DependencyTracker dependency_tracker;
+		};
+
+		Data *data = nullptr;
+
+		GeometryInstanceForwardMobile() :
+				dirty_list_element(this) {}
+	};
+
+public:
+	static void _geometry_instance_dependency_changed(RendererStorage::DependencyChangedNotification p_notification, RendererStorage::DependencyTracker *p_tracker);
+	static void _geometry_instance_dependency_deleted(const RID &p_dependency, RendererStorage::DependencyTracker *p_tracker);
+
+	SelfList<GeometryInstanceForwardMobile>::List geometry_instance_dirty_list;
+
+	PagedAllocator<GeometryInstanceForwardMobile> geometry_instance_alloc;
+	PagedAllocator<GeometryInstanceSurfaceDataCache> geometry_instance_surface_alloc;
+	PagedAllocator<GeometryInstanceLightmapSH> geometry_instance_lightmap_sh;
+
+	void _geometry_instance_add_surface_with_material(GeometryInstanceForwardMobile *ginstance, uint32_t p_surface, SceneShaderForwardMobile::MaterialData *p_material, uint32_t p_material_id, uint32_t p_shader_id, RID p_mesh);
+	void _geometry_instance_add_surface(GeometryInstanceForwardMobile *ginstance, uint32_t p_surface, RID p_material, RID p_mesh);
+	void _geometry_instance_mark_dirty(GeometryInstance *p_geometry_instance);
+	void _geometry_instance_update(GeometryInstance *p_geometry_instance);
+	void _update_dirty_geometry_instances();
+
+	virtual GeometryInstance *geometry_instance_create(RID p_base);
+	virtual void geometry_instance_set_skeleton(GeometryInstance *p_geometry_instance, RID p_skeleton);
+	virtual void geometry_instance_set_material_override(GeometryInstance *p_geometry_instance, RID p_override);
+	virtual void geometry_instance_set_surface_materials(GeometryInstance *p_geometry_instance, const Vector<RID> &p_materials);
+	virtual void geometry_instance_set_mesh_instance(GeometryInstance *p_geometry_instance, RID p_mesh_instance);
+	virtual void geometry_instance_set_transform(GeometryInstance *p_geometry_instance, const Transform &p_transform, const AABB &p_aabb, const AABB &p_transformed_aabb);
+	virtual void geometry_instance_set_layer_mask(GeometryInstance *p_geometry_instance, uint32_t p_layer_mask);
+	virtual void geometry_instance_set_lod_bias(GeometryInstance *p_geometry_instance, float p_lod_bias);
+	virtual void geometry_instance_set_use_baked_light(GeometryInstance *p_geometry_instance, bool p_enable);
+	virtual void geometry_instance_set_use_dynamic_gi(GeometryInstance *p_geometry_instance, bool p_enable);
+	virtual void geometry_instance_set_use_lightmap(GeometryInstance *p_geometry_instance, RID p_lightmap_instance, const Rect2 &p_lightmap_uv_scale, int p_lightmap_slice_index);
+	virtual void geometry_instance_set_lightmap_capture(GeometryInstance *p_geometry_instance, const Color *p_sh9);
+	virtual void geometry_instance_set_instance_shader_parameters_offset(GeometryInstance *p_geometry_instance, int32_t p_offset);
+	virtual void geometry_instance_set_cast_double_sided_shadows(GeometryInstance *p_geometry_instance, bool p_enable);
+
+	virtual Transform geometry_instance_get_transform(GeometryInstance *p_instance);
+	virtual AABB geometry_instance_get_aabb(GeometryInstance *p_instance);
+
+	virtual void geometry_instance_free(GeometryInstance *p_geometry_instance);
+
+	virtual uint32_t geometry_instance_get_pair_mask();
+	virtual void geometry_instance_pair_light_instances(GeometryInstance *p_geometry_instance, const RID *p_light_instances, uint32_t p_light_instance_count);
+	virtual void geometry_instance_pair_reflection_probe_instances(GeometryInstance *p_geometry_instance, const RID *p_reflection_probe_instances, uint32_t p_reflection_probe_instance_count);
+	virtual void geometry_instance_pair_decal_instances(GeometryInstance *p_geometry_instance, const RID *p_decal_instances, uint32_t p_decal_instance_count);
+	virtual void geometry_instance_pair_gi_probe_instances(GeometryInstance *p_geometry_instance, const RID *p_gi_probe_instances, uint32_t p_gi_probe_instance_count);
+
+	virtual bool free(RID p_rid);
+
+	virtual bool is_dynamic_gi_supported() const;
+	virtual bool is_clustered_enabled() const;
+	virtual bool is_volumetric_supported() const;
+	virtual uint32_t get_max_elements() const;
+
+	RenderForwardMobile(RendererStorageRD *p_storage);
+	~RenderForwardMobile();
+};
+} // namespace RendererSceneRenderImplementation
+#endif // !RENDERING_SERVER_SCENE_RENDER_FORWARD_MOBILE_H

+ 833 - 0
servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp

@@ -0,0 +1,833 @@
+/*************************************************************************/
+/*  scene_shader_forward_mobile.cpp                                      */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md).   */
+/*                                                                       */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the       */
+/* "Software"), to deal in the Software without restriction, including   */
+/* without limitation the rights to use, copy, modify, merge, publish,   */
+/* distribute, sublicense, and/or sell copies of the Software, and to    */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions:                                             */
+/*                                                                       */
+/* The above copyright notice and this permission notice shall be        */
+/* included in all copies or substantial portions of the Software.       */
+/*                                                                       */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
+/*************************************************************************/
+
+#include "scene_shader_forward_mobile.h"
+#include "core/config/project_settings.h"
+#include "render_forward_mobile.h"
+
+using namespace RendererSceneRenderImplementation;
+
+/* ShaderData */
+
+void SceneShaderForwardMobile::ShaderData::set_code(const String &p_code) {
+	//compile
+
+	code = p_code;
+	valid = false;
+	ubo_size = 0;
+	uniforms.clear();
+	uses_screen_texture = false;
+
+	if (code == String()) {
+		return; //just invalid, but no error
+	}
+
+	ShaderCompilerRD::GeneratedCode gen_code;
+
+	int blend_mode = BLEND_MODE_MIX;
+	int depth_testi = DEPTH_TEST_ENABLED;
+	int alpha_antialiasing_mode = ALPHA_ANTIALIASING_OFF;
+	int cull = CULL_BACK;
+
+	uses_point_size = false;
+	uses_alpha = false;
+	uses_blend_alpha = false;
+	uses_depth_pre_pass = false;
+	uses_discard = false;
+	uses_roughness = false;
+	uses_normal = false;
+	bool wireframe = false;
+
+	unshaded = false;
+	uses_vertex = false;
+	uses_sss = false;
+	uses_transmittance = false;
+	uses_screen_texture = false;
+	uses_depth_texture = false;
+	uses_normal_texture = false;
+	uses_time = false;
+	writes_modelview_or_projection = false;
+	uses_world_coordinates = false;
+	uses_particle_trails = false;
+
+	int depth_drawi = DEPTH_DRAW_OPAQUE;
+
+	ShaderCompilerRD::IdentifierActions actions;
+	actions.entry_point_stages["vertex"] = ShaderCompilerRD::STAGE_VERTEX;
+	actions.entry_point_stages["fragment"] = ShaderCompilerRD::STAGE_FRAGMENT;
+	actions.entry_point_stages["light"] = ShaderCompilerRD::STAGE_FRAGMENT;
+
+	actions.render_mode_values["blend_add"] = Pair<int *, int>(&blend_mode, BLEND_MODE_ADD);
+	actions.render_mode_values["blend_mix"] = Pair<int *, int>(&blend_mode, BLEND_MODE_MIX);
+	actions.render_mode_values["blend_sub"] = Pair<int *, int>(&blend_mode, BLEND_MODE_SUB);
+	actions.render_mode_values["blend_mul"] = Pair<int *, int>(&blend_mode, BLEND_MODE_MUL);
+
+	actions.render_mode_values["alpha_to_coverage"] = Pair<int *, int>(&alpha_antialiasing_mode, ALPHA_ANTIALIASING_ALPHA_TO_COVERAGE);
+	actions.render_mode_values["alpha_to_coverage_and_one"] = Pair<int *, int>(&alpha_antialiasing_mode, ALPHA_ANTIALIASING_ALPHA_TO_COVERAGE_AND_TO_ONE);
+
+	actions.render_mode_values["depth_draw_never"] = Pair<int *, int>(&depth_drawi, DEPTH_DRAW_DISABLED);
+	actions.render_mode_values["depth_draw_opaque"] = Pair<int *, int>(&depth_drawi, DEPTH_DRAW_OPAQUE);
+	actions.render_mode_values["depth_draw_always"] = Pair<int *, int>(&depth_drawi, DEPTH_DRAW_ALWAYS);
+
+	actions.render_mode_values["depth_test_disabled"] = Pair<int *, int>(&depth_testi, DEPTH_TEST_DISABLED);
+
+	actions.render_mode_values["cull_disabled"] = Pair<int *, int>(&cull, CULL_DISABLED);
+	actions.render_mode_values["cull_front"] = Pair<int *, int>(&cull, CULL_FRONT);
+	actions.render_mode_values["cull_back"] = Pair<int *, int>(&cull, CULL_BACK);
+
+	actions.render_mode_flags["unshaded"] = &unshaded;
+	actions.render_mode_flags["wireframe"] = &wireframe;
+	actions.render_mode_flags["particle_trails"] = &uses_particle_trails;
+
+	actions.usage_flag_pointers["ALPHA"] = &uses_alpha;
+	actions.render_mode_flags["depth_prepass_alpha"] = &uses_depth_pre_pass;
+
+	// actions.usage_flag_pointers["SSS_STRENGTH"] = &uses_sss;
+	// actions.usage_flag_pointers["SSS_TRANSMITTANCE_DEPTH"] = &uses_transmittance;
+
+	actions.usage_flag_pointers["SCREEN_TEXTURE"] = &uses_screen_texture;
+	actions.usage_flag_pointers["DEPTH_TEXTURE"] = &uses_depth_texture;
+	actions.usage_flag_pointers["NORMAL_TEXTURE"] = &uses_normal_texture;
+	actions.usage_flag_pointers["DISCARD"] = &uses_discard;
+	actions.usage_flag_pointers["TIME"] = &uses_time;
+	actions.usage_flag_pointers["ROUGHNESS"] = &uses_roughness;
+	actions.usage_flag_pointers["NORMAL"] = &uses_normal;
+	actions.usage_flag_pointers["NORMAL_MAP"] = &uses_normal;
+
+	actions.usage_flag_pointers["POINT_SIZE"] = &uses_point_size;
+	actions.usage_flag_pointers["POINT_COORD"] = &uses_point_size;
+
+	actions.write_flag_pointers["MODELVIEW_MATRIX"] = &writes_modelview_or_projection;
+	actions.write_flag_pointers["PROJECTION_MATRIX"] = &writes_modelview_or_projection;
+	actions.write_flag_pointers["VERTEX"] = &uses_vertex;
+
+	actions.uniforms = &uniforms;
+
+	SceneShaderForwardMobile *shader_singleton = (SceneShaderForwardMobile *)SceneShaderForwardMobile::singleton;
+
+	Error err = shader_singleton->compiler.compile(RS::SHADER_SPATIAL, code, &actions, path, gen_code);
+
+	ERR_FAIL_COND(err != OK);
+
+	if (version.is_null()) {
+		version = shader_singleton->shader.version_create();
+	}
+
+	depth_draw = DepthDraw(depth_drawi);
+	depth_test = DepthTest(depth_testi);
+
+#if 0
+	print_line("**compiling shader:");
+	print_line("**defines:\n");
+	for (int i = 0; i < gen_code.defines.size(); i++) {
+		print_line(gen_code.defines[i]);
+	}
+
+	Map<String, String>::Element * el = gen_code.code.front();
+	while (el) {
+		print_line("\n**code " + el->key() + ":\n" + el->value());
+
+		el = el->next();
+	}
+
+	print_line("\n**uniforms:\n" + gen_code.uniforms);
+	print_line("\n**vertex_globals:\n" + gen_code.stage_globals[ShaderCompilerRD::STAGE_VERTEX]);
+	print_line("\n**fragment_globals:\n" + gen_code.stage_globals[ShaderCompilerRD::STAGE_FRAGMENT]);
+#endif
+
+	shader_singleton->shader.version_set_code(version, gen_code.code, gen_code.uniforms, gen_code.stage_globals[ShaderCompilerRD::STAGE_VERTEX], gen_code.stage_globals[ShaderCompilerRD::STAGE_FRAGMENT], gen_code.defines);
+	ERR_FAIL_COND(!shader_singleton->shader.version_is_valid(version));
+
+	ubo_size = gen_code.uniform_total_size;
+	ubo_offsets = gen_code.uniform_offsets;
+	texture_uniforms = gen_code.texture_uniforms;
+
+	//blend modes
+
+	// if any form of Alpha Antialiasing is enabled, set the blend mode to alpha to coverage
+	if (alpha_antialiasing_mode != ALPHA_ANTIALIASING_OFF) {
+		blend_mode = BLEND_MODE_ALPHA_TO_COVERAGE;
+	}
+
+	RD::PipelineColorBlendState::Attachment blend_attachment;
+
+	switch (blend_mode) {
+		case BLEND_MODE_MIX: {
+			blend_attachment.enable_blend = true;
+			blend_attachment.alpha_blend_op = RD::BLEND_OP_ADD;
+			blend_attachment.color_blend_op = RD::BLEND_OP_ADD;
+			blend_attachment.src_color_blend_factor = RD::BLEND_FACTOR_SRC_ALPHA;
+			blend_attachment.dst_color_blend_factor = RD::BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
+			blend_attachment.src_alpha_blend_factor = RD::BLEND_FACTOR_ONE;
+			blend_attachment.dst_alpha_blend_factor = RD::BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
+
+		} break;
+		case BLEND_MODE_ADD: {
+			blend_attachment.enable_blend = true;
+			blend_attachment.alpha_blend_op = RD::BLEND_OP_ADD;
+			blend_attachment.color_blend_op = RD::BLEND_OP_ADD;
+			blend_attachment.src_color_blend_factor = RD::BLEND_FACTOR_SRC_ALPHA;
+			blend_attachment.dst_color_blend_factor = RD::BLEND_FACTOR_ONE;
+			blend_attachment.src_alpha_blend_factor = RD::BLEND_FACTOR_SRC_ALPHA;
+			blend_attachment.dst_alpha_blend_factor = RD::BLEND_FACTOR_ONE;
+			uses_blend_alpha = true; //force alpha used because of blend
+
+		} break;
+		case BLEND_MODE_SUB: {
+			blend_attachment.enable_blend = true;
+			blend_attachment.alpha_blend_op = RD::BLEND_OP_SUBTRACT;
+			blend_attachment.color_blend_op = RD::BLEND_OP_SUBTRACT;
+			blend_attachment.src_color_blend_factor = RD::BLEND_FACTOR_SRC_ALPHA;
+			blend_attachment.dst_color_blend_factor = RD::BLEND_FACTOR_ONE;
+			blend_attachment.src_alpha_blend_factor = RD::BLEND_FACTOR_SRC_ALPHA;
+			blend_attachment.dst_alpha_blend_factor = RD::BLEND_FACTOR_ONE;
+			uses_blend_alpha = true; //force alpha used because of blend
+
+		} break;
+		case BLEND_MODE_MUL: {
+			blend_attachment.enable_blend = true;
+			blend_attachment.alpha_blend_op = RD::BLEND_OP_ADD;
+			blend_attachment.color_blend_op = RD::BLEND_OP_ADD;
+			blend_attachment.src_color_blend_factor = RD::BLEND_FACTOR_DST_COLOR;
+			blend_attachment.dst_color_blend_factor = RD::BLEND_FACTOR_ZERO;
+			blend_attachment.src_alpha_blend_factor = RD::BLEND_FACTOR_DST_ALPHA;
+			blend_attachment.dst_alpha_blend_factor = RD::BLEND_FACTOR_ZERO;
+			uses_blend_alpha = true; //force alpha used because of blend
+		} break;
+		case BLEND_MODE_ALPHA_TO_COVERAGE: {
+			blend_attachment.enable_blend = true;
+			blend_attachment.alpha_blend_op = RD::BLEND_OP_ADD;
+			blend_attachment.color_blend_op = RD::BLEND_OP_ADD;
+			blend_attachment.src_color_blend_factor = RD::BLEND_FACTOR_SRC_ALPHA;
+			blend_attachment.dst_color_blend_factor = RD::BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
+			blend_attachment.src_alpha_blend_factor = RD::BLEND_FACTOR_ONE;
+			blend_attachment.dst_alpha_blend_factor = RD::BLEND_FACTOR_ZERO;
+		}
+	}
+
+	RD::PipelineColorBlendState blend_state_blend;
+	blend_state_blend.attachments.push_back(blend_attachment);
+	RD::PipelineColorBlendState blend_state_opaque = RD::PipelineColorBlendState::create_disabled(1);
+	RD::PipelineColorBlendState blend_state_opaque_specular = RD::PipelineColorBlendState::create_disabled(2);
+	RD::PipelineColorBlendState blend_state_depth_normal_roughness = RD::PipelineColorBlendState::create_disabled(1);
+	RD::PipelineColorBlendState blend_state_depth_normal_roughness_giprobe = RD::PipelineColorBlendState::create_disabled(2);
+
+	//update pipelines
+
+	RD::PipelineDepthStencilState depth_stencil_state;
+
+	if (depth_test != DEPTH_TEST_DISABLED) {
+		depth_stencil_state.enable_depth_test = true;
+		depth_stencil_state.depth_compare_operator = RD::COMPARE_OP_LESS_OR_EQUAL;
+		depth_stencil_state.enable_depth_write = depth_draw != DEPTH_DRAW_DISABLED ? true : false;
+	}
+
+	for (int i = 0; i < CULL_VARIANT_MAX; i++) {
+		RD::PolygonCullMode cull_mode_rd_table[CULL_VARIANT_MAX][3] = {
+			{ RD::POLYGON_CULL_DISABLED, RD::POLYGON_CULL_FRONT, RD::POLYGON_CULL_BACK },
+			{ RD::POLYGON_CULL_DISABLED, RD::POLYGON_CULL_BACK, RD::POLYGON_CULL_FRONT },
+			{ RD::POLYGON_CULL_DISABLED, RD::POLYGON_CULL_DISABLED, RD::POLYGON_CULL_DISABLED }
+		};
+
+		RD::PolygonCullMode cull_mode_rd = cull_mode_rd_table[i][cull];
+
+		for (int j = 0; j < RS::PRIMITIVE_MAX; j++) {
+			RD::RenderPrimitive primitive_rd_table[RS::PRIMITIVE_MAX] = {
+				RD::RENDER_PRIMITIVE_POINTS,
+				RD::RENDER_PRIMITIVE_LINES,
+				RD::RENDER_PRIMITIVE_LINESTRIPS,
+				RD::RENDER_PRIMITIVE_TRIANGLES,
+				RD::RENDER_PRIMITIVE_TRIANGLE_STRIPS,
+			};
+
+			RD::RenderPrimitive primitive_rd = uses_point_size ? RD::RENDER_PRIMITIVE_POINTS : primitive_rd_table[j];
+
+			for (int k = 0; k < SHADER_VERSION_MAX; k++) {
+				if (!static_cast<SceneShaderForwardMobile *>(singleton)->shader.is_variant_enabled(k)) {
+					continue;
+				}
+				RD::PipelineRasterizationState raster_state;
+				raster_state.cull_mode = cull_mode_rd;
+				raster_state.wireframe = wireframe;
+
+				RD::PipelineColorBlendState blend_state;
+				RD::PipelineDepthStencilState depth_stencil = depth_stencil_state;
+				RD::PipelineMultisampleState multisample_state;
+
+				if (uses_alpha || uses_blend_alpha) {
+					// only allow these flags to go through if we have some form of msaa
+					if (alpha_antialiasing_mode == ALPHA_ANTIALIASING_ALPHA_TO_COVERAGE) {
+						multisample_state.enable_alpha_to_coverage = true;
+					} else if (alpha_antialiasing_mode == ALPHA_ANTIALIASING_ALPHA_TO_COVERAGE_AND_TO_ONE) {
+						multisample_state.enable_alpha_to_coverage = true;
+						multisample_state.enable_alpha_to_one = true;
+					}
+
+					if (k == SHADER_VERSION_COLOR_PASS || k == SHADER_VERSION_LIGHTMAP_COLOR_PASS) {
+						blend_state = blend_state_blend;
+						if (depth_draw == DEPTH_DRAW_OPAQUE) {
+							depth_stencil.enable_depth_write = false; //alpha does not draw depth
+						}
+					} else if (k == SHADER_VERSION_SHADOW_PASS || k == SHADER_VERSION_DEPTH_PASS_DP) {
+						//none, blend state contains nothing
+					} else if (k == SHADER_VERSION_DEPTH_PASS_WITH_MATERIAL) {
+						blend_state = RD::PipelineColorBlendState::create_disabled(5); //writes to normal and roughness in opaque way
+					} else {
+						pipelines[i][j][k].clear();
+						continue; // do not use this version (will error if using it is attempted)
+					}
+
+					/*
+					if (k == SHADER_VERSION_COLOR_PASS || k == SHADER_VERSION_COLOR_PASS_WITH_FORWARD_GI || k == SHADER_VERSION_LIGHTMAP_COLOR_PASS) {
+						blend_state = blend_state_blend;
+						if (depth_draw == DEPTH_DRAW_OPAQUE) {
+							depth_stencil.enable_depth_write = false; //alpha does not draw depth
+						}
+					} else if (uses_depth_pre_pass && (k == SHADER_VERSION_DEPTH_PASS || k == SHADER_VERSION_DEPTH_PASS_DP || k == SHADER_VERSION_DEPTH_PASS_WITH_NORMAL_AND_ROUGHNESS || k == SHADER_VERSION_DEPTH_PASS_WITH_MATERIAL)) {
+						if (k == SHADER_VERSION_DEPTH_PASS || k == SHADER_VERSION_DEPTH_PASS_DP) {
+							//none, blend state contains nothing
+						} else if (k == SHADER_VERSION_DEPTH_PASS_WITH_MATERIAL) {
+							blend_state = RD::PipelineColorBlendState::create_disabled(5); //writes to normal and roughness in opaque way
+						} else {
+							blend_state = blend_state_opaque; //writes to normal and roughness in opaque way
+						}
+					} else {
+						pipelines[i][j][k].clear();
+						continue; // do not use this version (will error if using it is attempted)
+					}
+					*/
+				} else {
+					if (k == SHADER_VERSION_COLOR_PASS || k == SHADER_VERSION_LIGHTMAP_COLOR_PASS) {
+						blend_state = blend_state_opaque;
+					} else if (k == SHADER_VERSION_SHADOW_PASS || k == SHADER_VERSION_DEPTH_PASS_DP) {
+						//none, leave empty
+					} else if (k == SHADER_VERSION_DEPTH_PASS_WITH_MATERIAL) {
+						blend_state = RD::PipelineColorBlendState::create_disabled(5); //writes to normal and roughness in opaque way
+					} else {
+						// ???
+					}
+
+					/*
+					if (k == SHADER_VERSION_COLOR_PASS || k == SHADER_VERSION_COLOR_PASS_WITH_FORWARD_GI || k == SHADER_VERSION_LIGHTMAP_COLOR_PASS) {
+						blend_state = blend_state_opaque;
+					} else if (k == SHADER_VERSION_DEPTH_PASS || k == SHADER_VERSION_DEPTH_PASS_DP) {
+						//none, leave empty
+					} else if (k == SHADER_VERSION_DEPTH_PASS_WITH_NORMAL_AND_ROUGHNESS) {
+						blend_state = blend_state_depth_normal_roughness;
+					} else if (k == SHADER_VERSION_DEPTH_PASS_WITH_NORMAL_AND_ROUGHNESS_AND_GIPROBE) {
+						blend_state = blend_state_depth_normal_roughness_giprobe;
+					} else if (k == SHADER_VERSION_DEPTH_PASS_WITH_MATERIAL) {
+						blend_state = RD::PipelineColorBlendState::create_disabled(5); //writes to normal and roughness in opaque way
+					} else if (k == SHADER_VERSION_DEPTH_PASS_WITH_SDF) {
+						blend_state = RD::PipelineColorBlendState(); //no color targets for SDF
+					} else {
+						//specular write
+						blend_state = blend_state_opaque_specular;
+						depth_stencil.enable_depth_test = false;
+						depth_stencil.enable_depth_write = false;
+					}
+					*/
+				}
+
+				RID shader_variant = shader_singleton->shader.version_get_shader(version, k);
+				pipelines[i][j][k].setup(shader_variant, primitive_rd, raster_state, multisample_state, depth_stencil, blend_state, 0);
+			}
+		}
+	}
+
+	valid = true;
+}
+
+void SceneShaderForwardMobile::ShaderData::set_default_texture_param(const StringName &p_name, RID p_texture) {
+	if (!p_texture.is_valid()) {
+		default_texture_params.erase(p_name);
+	} else {
+		default_texture_params[p_name] = p_texture;
+	}
+}
+
+void SceneShaderForwardMobile::ShaderData::get_param_list(List<PropertyInfo> *p_param_list) const {
+	Map<int, StringName> order;
+
+	for (Map<StringName, ShaderLanguage::ShaderNode::Uniform>::Element *E = uniforms.front(); E; E = E->next()) {
+		if (E->get().scope != ShaderLanguage::ShaderNode::Uniform::SCOPE_LOCAL) {
+			continue;
+		}
+
+		if (E->get().texture_order >= 0) {
+			order[E->get().texture_order + 100000] = E->key();
+		} else {
+			order[E->get().order] = E->key();
+		}
+	}
+
+	for (Map<int, StringName>::Element *E = order.front(); E; E = E->next()) {
+		PropertyInfo pi = ShaderLanguage::uniform_to_property_info(uniforms[E->get()]);
+		pi.name = E->get();
+		p_param_list->push_back(pi);
+	}
+}
+
+void SceneShaderForwardMobile::ShaderData::get_instance_param_list(List<RendererStorage::InstanceShaderParam> *p_param_list) const {
+	for (Map<StringName, ShaderLanguage::ShaderNode::Uniform>::Element *E = uniforms.front(); E; E = E->next()) {
+		if (E->get().scope != ShaderLanguage::ShaderNode::Uniform::SCOPE_INSTANCE) {
+			continue;
+		}
+
+		RendererStorage::InstanceShaderParam p;
+		p.info = ShaderLanguage::uniform_to_property_info(E->get());
+		p.info.name = E->key(); //supply name
+		p.index = E->get().instance_index;
+		p.default_value = ShaderLanguage::constant_value_to_variant(E->get().default_value, E->get().type, E->get().hint);
+		p_param_list->push_back(p);
+	}
+}
+
+bool SceneShaderForwardMobile::ShaderData::is_param_texture(const StringName &p_param) const {
+	if (!uniforms.has(p_param)) {
+		return false;
+	}
+
+	return uniforms[p_param].texture_order >= 0;
+}
+
+bool SceneShaderForwardMobile::ShaderData::is_animated() const {
+	return false;
+}
+
+bool SceneShaderForwardMobile::ShaderData::casts_shadows() const {
+	return false;
+}
+
+Variant SceneShaderForwardMobile::ShaderData::get_default_parameter(const StringName &p_parameter) const {
+	if (uniforms.has(p_parameter)) {
+		ShaderLanguage::ShaderNode::Uniform uniform = uniforms[p_parameter];
+		Vector<ShaderLanguage::ConstantNode::Value> default_value = uniform.default_value;
+		return ShaderLanguage::constant_value_to_variant(default_value, uniform.type, uniform.hint);
+	}
+	return Variant();
+}
+
+RS::ShaderNativeSourceCode SceneShaderForwardMobile::ShaderData::get_native_source_code() const {
+	SceneShaderForwardMobile *shader_singleton = (SceneShaderForwardMobile *)SceneShaderForwardMobile::singleton;
+
+	return shader_singleton->shader.version_get_native_source_code(version);
+}
+
+SceneShaderForwardMobile::ShaderData::ShaderData() {
+	valid = false;
+	uses_screen_texture = false;
+}
+
+SceneShaderForwardMobile::ShaderData::~ShaderData() {
+	SceneShaderForwardMobile *shader_singleton = (SceneShaderForwardMobile *)SceneShaderForwardMobile::singleton;
+	ERR_FAIL_COND(!shader_singleton);
+	//pipeline variants will clear themselves if shader is gone
+	if (version.is_valid()) {
+		shader_singleton->shader.version_free(version);
+	}
+}
+
+RendererStorageRD::ShaderData *SceneShaderForwardMobile::_create_shader_func() {
+	ShaderData *shader_data = memnew(ShaderData);
+	return shader_data;
+}
+
+void SceneShaderForwardMobile::MaterialData::set_render_priority(int p_priority) {
+	priority = p_priority - RS::MATERIAL_RENDER_PRIORITY_MIN; //8 bits
+}
+
+void SceneShaderForwardMobile::MaterialData::set_next_pass(RID p_pass) {
+	next_pass = p_pass;
+}
+
+void SceneShaderForwardMobile::MaterialData::update_parameters(const Map<StringName, Variant> &p_parameters, bool p_uniform_dirty, bool p_textures_dirty) {
+	SceneShaderForwardMobile *shader_singleton = (SceneShaderForwardMobile *)SceneShaderForwardMobile::singleton;
+
+	if ((uint32_t)ubo_data.size() != shader_data->ubo_size) {
+		p_uniform_dirty = true;
+		if (uniform_buffer.is_valid()) {
+			RD::get_singleton()->free(uniform_buffer);
+			uniform_buffer = RID();
+		}
+
+		ubo_data.resize(shader_data->ubo_size);
+		if (ubo_data.size()) {
+			uniform_buffer = RD::get_singleton()->uniform_buffer_create(ubo_data.size());
+			memset(ubo_data.ptrw(), 0, ubo_data.size()); //clear
+		}
+
+		//clear previous uniform set
+		if (uniform_set.is_valid() && RD::get_singleton()->uniform_set_is_valid(uniform_set)) {
+			RD::get_singleton()->free(uniform_set);
+			uniform_set = RID();
+		}
+	}
+
+	//check whether buffer changed
+	if (p_uniform_dirty && ubo_data.size()) {
+		update_uniform_buffer(shader_data->uniforms, shader_data->ubo_offsets.ptr(), p_parameters, ubo_data.ptrw(), ubo_data.size(), false);
+		RD::get_singleton()->buffer_update(uniform_buffer, 0, ubo_data.size(), ubo_data.ptrw(), RD::BARRIER_MASK_RASTER);
+	}
+
+	uint32_t tex_uniform_count = shader_data->texture_uniforms.size();
+
+	if ((uint32_t)texture_cache.size() != tex_uniform_count) {
+		texture_cache.resize(tex_uniform_count);
+		p_textures_dirty = true;
+
+		//clear previous uniform set
+		if (uniform_set.is_valid() && RD::get_singleton()->uniform_set_is_valid(uniform_set)) {
+			RD::get_singleton()->free(uniform_set);
+			uniform_set = RID();
+		}
+	}
+
+	if (p_textures_dirty && tex_uniform_count) {
+		update_textures(p_parameters, shader_data->default_texture_params, shader_data->texture_uniforms, texture_cache.ptrw(), true);
+	}
+
+	if (shader_data->ubo_size == 0 && shader_data->texture_uniforms.size() == 0) {
+		// This material does not require an uniform set, so don't create it.
+		return;
+	}
+
+	if (!p_textures_dirty && uniform_set.is_valid() && RD::get_singleton()->uniform_set_is_valid(uniform_set)) {
+		//no reason to update uniform set, only UBO (or nothing) was needed to update
+		return;
+	}
+
+	Vector<RD::Uniform> uniforms;
+
+	{
+		if (shader_data->ubo_size) {
+			RD::Uniform u;
+			u.uniform_type = RD::UNIFORM_TYPE_UNIFORM_BUFFER;
+			u.binding = 0;
+			u.ids.push_back(uniform_buffer);
+			uniforms.push_back(u);
+		}
+
+		const RID *textures = texture_cache.ptrw();
+		for (uint32_t i = 0; i < tex_uniform_count; i++) {
+			RD::Uniform u;
+			u.uniform_type = RD::UNIFORM_TYPE_TEXTURE;
+			u.binding = 1 + i;
+			u.ids.push_back(textures[i]);
+			uniforms.push_back(u);
+		}
+	}
+
+	uniform_set = RD::get_singleton()->uniform_set_create(uniforms, shader_singleton->shader.version_get_shader(shader_data->version, 0), RenderForwardMobile::MATERIAL_UNIFORM_SET);
+}
+
+SceneShaderForwardMobile::MaterialData::~MaterialData() {
+	if (uniform_set.is_valid() && RD::get_singleton()->uniform_set_is_valid(uniform_set)) {
+		RD::get_singleton()->free(uniform_set);
+	}
+
+	if (uniform_buffer.is_valid()) {
+		RD::get_singleton()->free(uniform_buffer);
+	}
+}
+
+RendererStorageRD::MaterialData *SceneShaderForwardMobile::_create_material_func(ShaderData *p_shader) {
+	MaterialData *material_data = memnew(MaterialData);
+	material_data->shader_data = p_shader;
+	material_data->last_frame = false;
+	//update will happen later anyway so do nothing.
+	return material_data;
+}
+
+/* Scene Shader */
+
+SceneShaderForwardMobile *SceneShaderForwardMobile::singleton = nullptr;
+
+SceneShaderForwardMobile::SceneShaderForwardMobile() {
+	// there should be only one of these, contained within our RenderForwardMobile singleton.
+	singleton = this;
+}
+
+void SceneShaderForwardMobile::init(RendererStorageRD *p_storage, const String p_defines) {
+	storage = p_storage;
+
+	/* SCENE SHADER */
+
+	{
+		Vector<String> shader_versions;
+		shader_versions.push_back(""); // SHADER_VERSION_COLOR_PASS
+		shader_versions.push_back("\n#define USE_LIGHTMAP\n"); // SHADER_VERSION_LIGHTMAP_COLOR_PASS
+		shader_versions.push_back("\n#define MODE_RENDER_DEPTH\n"); // !BAS! SHADER_VERSION_SHADOW_PASS, should probably change this to MODE_RENDER_SHADOW because we don't have a depth pass here...
+		shader_versions.push_back("\n#define MODE_RENDER_DEPTH\n#define MODE_DUAL_PARABOLOID\n"); // SHADER_VERSION_DEPTH_PASS_DP (maybe rename to SHADER_VERSION_SHADOW_PASS_DP?)
+		shader_versions.push_back("\n#define MODE_RENDER_DEPTH\n#define MODE_RENDER_MATERIAL\n"); // SHADER_VERSION_DEPTH_PASS_WITH_MATERIAL
+		shader.initialize(shader_versions, p_defines);
+	}
+
+	storage->shader_set_data_request_function(RendererStorageRD::SHADER_TYPE_3D, _create_shader_funcs);
+	storage->material_set_data_request_function(RendererStorageRD::SHADER_TYPE_3D, _create_material_funcs);
+
+	{
+		//shader compiler
+		ShaderCompilerRD::DefaultIdentifierActions actions;
+
+		actions.renames["WORLD_MATRIX"] = "world_matrix";
+		actions.renames["WORLD_NORMAL_MATRIX"] = "world_normal_matrix";
+		actions.renames["INV_CAMERA_MATRIX"] = "scene_data.inv_camera_matrix";
+		actions.renames["CAMERA_MATRIX"] = "scene_data.camera_matrix";
+		actions.renames["PROJECTION_MATRIX"] = "projection_matrix";
+		actions.renames["INV_PROJECTION_MATRIX"] = "scene_data.inv_projection_matrix";
+		actions.renames["MODELVIEW_MATRIX"] = "modelview";
+		actions.renames["MODELVIEW_NORMAL_MATRIX"] = "modelview_normal";
+
+		actions.renames["VERTEX"] = "vertex";
+		actions.renames["NORMAL"] = "normal";
+		actions.renames["TANGENT"] = "tangent";
+		actions.renames["BINORMAL"] = "binormal";
+		actions.renames["POSITION"] = "position";
+		actions.renames["UV"] = "uv_interp";
+		actions.renames["UV2"] = "uv2_interp";
+		actions.renames["COLOR"] = "color_interp";
+		actions.renames["POINT_SIZE"] = "gl_PointSize";
+		actions.renames["INSTANCE_ID"] = "gl_InstanceIndex";
+
+		actions.renames["ALPHA_SCISSOR_THRESHOLD"] = "alpha_scissor_threshold";
+		actions.renames["ALPHA_HASH_SCALE"] = "alpha_hash_scale";
+		actions.renames["ALPHA_ANTIALIASING_EDGE"] = "alpha_antialiasing_edge";
+		actions.renames["ALPHA_TEXTURE_COORDINATE"] = "alpha_texture_coordinate";
+
+		//builtins
+
+		actions.renames["TIME"] = "scene_data.time";
+		actions.renames["VIEWPORT_SIZE"] = "scene_data.viewport_size";
+
+		actions.renames["FRAGCOORD"] = "gl_FragCoord";
+		actions.renames["FRONT_FACING"] = "gl_FrontFacing";
+		actions.renames["NORMAL_MAP"] = "normal_map";
+		actions.renames["NORMAL_MAP_DEPTH"] = "normal_map_depth";
+		actions.renames["ALBEDO"] = "albedo";
+		actions.renames["ALPHA"] = "alpha";
+		actions.renames["METALLIC"] = "metallic";
+		actions.renames["SPECULAR"] = "specular";
+		actions.renames["ROUGHNESS"] = "roughness";
+		actions.renames["RIM"] = "rim";
+		actions.renames["RIM_TINT"] = "rim_tint";
+		actions.renames["CLEARCOAT"] = "clearcoat";
+		actions.renames["CLEARCOAT_GLOSS"] = "clearcoat_gloss";
+		actions.renames["ANISOTROPY"] = "anisotropy";
+		actions.renames["ANISOTROPY_FLOW"] = "anisotropy_flow";
+		actions.renames["SSS_STRENGTH"] = "sss_strength";
+		actions.renames["SSS_TRANSMITTANCE_COLOR"] = "transmittance_color";
+		actions.renames["SSS_TRANSMITTANCE_DEPTH"] = "transmittance_depth";
+		actions.renames["SSS_TRANSMITTANCE_CURVE"] = "transmittance_curve";
+		actions.renames["SSS_TRANSMITTANCE_BOOST"] = "transmittance_boost";
+		actions.renames["BACKLIGHT"] = "backlight";
+		actions.renames["AO"] = "ao";
+		actions.renames["AO_LIGHT_AFFECT"] = "ao_light_affect";
+		actions.renames["EMISSION"] = "emission";
+		actions.renames["POINT_COORD"] = "gl_PointCoord";
+		actions.renames["INSTANCE_CUSTOM"] = "instance_custom";
+		actions.renames["SCREEN_UV"] = "screen_uv";
+		actions.renames["SCREEN_TEXTURE"] = "color_buffer";
+		actions.renames["DEPTH_TEXTURE"] = "depth_buffer";
+		actions.renames["NORMAL_ROUGHNESS_TEXTURE"] = "normal_roughness_buffer";
+		actions.renames["DEPTH"] = "gl_FragDepth";
+		actions.renames["OUTPUT_IS_SRGB"] = "true";
+		actions.renames["FOG"] = "custom_fog";
+		actions.renames["RADIANCE"] = "custom_radiance";
+		actions.renames["IRRADIANCE"] = "custom_irradiance";
+		actions.renames["BONE_INDICES"] = "bone_attrib";
+		actions.renames["BONE_WEIGHTS"] = "weight_attrib";
+		actions.renames["CUSTOM0"] = "custom0_attrib";
+		actions.renames["CUSTOM1"] = "custom1_attrib";
+		actions.renames["CUSTOM2"] = "custom2_attrib";
+		actions.renames["CUSTOM3"] = "custom3_attrib";
+
+		//for light
+		actions.renames["VIEW"] = "view";
+		actions.renames["LIGHT_COLOR"] = "light_color";
+		actions.renames["LIGHT"] = "light";
+		actions.renames["ATTENUATION"] = "attenuation";
+		actions.renames["SHADOW_ATTENUATION"] = "shadow_attenuation";
+		actions.renames["DIFFUSE_LIGHT"] = "diffuse_light";
+		actions.renames["SPECULAR_LIGHT"] = "specular_light";
+
+		actions.usage_defines["NORMAL"] = "#define NORMAL_USED\n";
+		actions.usage_defines["TANGENT"] = "#define TANGENT_USED\n";
+		actions.usage_defines["BINORMAL"] = "@TANGENT";
+		actions.usage_defines["RIM"] = "#define LIGHT_RIM_USED\n";
+		actions.usage_defines["RIM_TINT"] = "@RIM";
+		actions.usage_defines["CLEARCOAT"] = "#define LIGHT_CLEARCOAT_USED\n";
+		actions.usage_defines["CLEARCOAT_GLOSS"] = "@CLEARCOAT";
+		actions.usage_defines["ANISOTROPY"] = "#define LIGHT_ANISOTROPY_USED\n";
+		actions.usage_defines["ANISOTROPY_FLOW"] = "@ANISOTROPY";
+		actions.usage_defines["AO"] = "#define AO_USED\n";
+		actions.usage_defines["AO_LIGHT_AFFECT"] = "#define AO_USED\n";
+		actions.usage_defines["UV"] = "#define UV_USED\n";
+		actions.usage_defines["UV2"] = "#define UV2_USED\n";
+		actions.usage_defines["BONE_INDICES"] = "#define BONES_USED\n";
+		actions.usage_defines["BONE_WEIGHTS"] = "#define WEIGHTS_USED\n";
+		actions.usage_defines["CUSTOM0"] = "#define CUSTOM0\n";
+		actions.usage_defines["CUSTOM1"] = "#define CUSTOM1\n";
+		actions.usage_defines["CUSTOM2"] = "#define CUSTOM2\n";
+		actions.usage_defines["CUSTOM3"] = "#define CUSTOM3\n";
+		actions.usage_defines["NORMAL_MAP"] = "#define NORMAL_MAP_USED\n";
+		actions.usage_defines["NORMAL_MAP_DEPTH"] = "@NORMAL_MAP";
+		actions.usage_defines["COLOR"] = "#define COLOR_USED\n";
+		actions.usage_defines["INSTANCE_CUSTOM"] = "#define ENABLE_INSTANCE_CUSTOM\n";
+		actions.usage_defines["POSITION"] = "#define OVERRIDE_POSITION\n";
+
+		actions.usage_defines["ALPHA_SCISSOR_THRESHOLD"] = "#define ALPHA_SCISSOR_USED\n";
+		actions.usage_defines["ALPHA_HASH_SCALE"] = "#define ALPHA_HASH_USED\n";
+		actions.usage_defines["ALPHA_ANTIALIASING_EDGE"] = "#define ALPHA_ANTIALIASING_EDGE_USED\n";
+		actions.usage_defines["ALPHA_TEXTURE_COORDINATE"] = "@ALPHA_ANTIALIASING_EDGE";
+
+		actions.usage_defines["SSS_STRENGTH"] = "#define ENABLE_SSS\n";
+		actions.usage_defines["SSS_TRANSMITTANCE_DEPTH"] = "#define ENABLE_TRANSMITTANCE\n";
+		actions.usage_defines["BACKLIGHT"] = "#define LIGHT_BACKLIGHT_USED\n";
+		actions.usage_defines["SCREEN_TEXTURE"] = "#define SCREEN_TEXTURE_USED\n";
+		actions.usage_defines["SCREEN_UV"] = "#define SCREEN_UV_USED\n";
+
+		actions.usage_defines["DIFFUSE_LIGHT"] = "#define USE_LIGHT_SHADER_CODE\n";
+		actions.usage_defines["SPECULAR_LIGHT"] = "#define USE_LIGHT_SHADER_CODE\n";
+
+		actions.usage_defines["FOG"] = "#define CUSTOM_FOG_USED\n";
+		actions.usage_defines["RADIANCE"] = "#define CUSTOM_RADIANCE_USED\n";
+		actions.usage_defines["IRRADIANCE"] = "#define CUSTOM_IRRADIANCE_USED\n";
+
+		actions.render_mode_defines["skip_vertex_transform"] = "#define SKIP_TRANSFORM_USED\n";
+		actions.render_mode_defines["world_vertex_coords"] = "#define VERTEX_WORLD_COORDS_USED\n";
+		actions.render_mode_defines["ensure_correct_normals"] = "#define ENSURE_CORRECT_NORMALS\n";
+		actions.render_mode_defines["cull_front"] = "#define DO_SIDE_CHECK\n";
+		actions.render_mode_defines["cull_disabled"] = "#define DO_SIDE_CHECK\n";
+		actions.render_mode_defines["particle_trails"] = "#define USE_PARTICLE_TRAILS\n";
+
+		bool force_lambert = GLOBAL_GET("rendering/shading/overrides/force_lambert_over_burley");
+		if (!force_lambert) {
+			actions.render_mode_defines["diffuse_burley"] = "#define DIFFUSE_BURLEY\n";
+		}
+
+		actions.render_mode_defines["diffuse_oren_nayar"] = "#define DIFFUSE_OREN_NAYAR\n";
+		actions.render_mode_defines["diffuse_lambert_wrap"] = "#define DIFFUSE_LAMBERT_WRAP\n";
+		actions.render_mode_defines["diffuse_toon"] = "#define DIFFUSE_TOON\n";
+
+		actions.render_mode_defines["sss_mode_skin"] = "#define SSS_MODE_SKIN\n";
+
+		bool force_blinn = GLOBAL_GET("rendering/shading/overrides/force_blinn_over_ggx");
+		if (!force_blinn) {
+			actions.render_mode_defines["specular_schlick_ggx"] = "#define SPECULAR_SCHLICK_GGX\n";
+		} else {
+			actions.render_mode_defines["specular_schlick_ggx"] = "#define SPECULAR_BLINN\n";
+		}
+
+		actions.render_mode_defines["specular_blinn"] = "#define SPECULAR_BLINN\n";
+		actions.render_mode_defines["specular_phong"] = "#define SPECULAR_PHONG\n";
+		actions.render_mode_defines["specular_toon"] = "#define SPECULAR_TOON\n";
+		actions.render_mode_defines["specular_disabled"] = "#define SPECULAR_DISABLED\n";
+		actions.render_mode_defines["shadows_disabled"] = "#define SHADOWS_DISABLED\n";
+		actions.render_mode_defines["ambient_light_disabled"] = "#define AMBIENT_LIGHT_DISABLED\n";
+		actions.render_mode_defines["shadow_to_opacity"] = "#define USE_SHADOW_TO_OPACITY\n";
+		actions.render_mode_defines["unshaded"] = "#define MODE_UNSHADED\n";
+
+		actions.sampler_array_name = "material_samplers";
+		actions.base_texture_binding_index = 1;
+		actions.texture_layout_set = RenderForwardMobile::MATERIAL_UNIFORM_SET;
+		actions.base_uniform_string = "material.";
+		actions.base_varying_index = 10;
+
+		actions.default_filter = ShaderLanguage::FILTER_LINEAR_MIPMAP;
+		actions.default_repeat = ShaderLanguage::REPEAT_ENABLE;
+		actions.global_buffer_array_variable = "global_variables.data";
+		actions.instance_uniform_index_variable = "draw_call.instance_uniforms_ofs";
+
+		compiler.initialize(actions);
+	}
+
+	{
+		//default material and shader
+		default_shader = storage->shader_allocate();
+		storage->shader_initialize(default_shader);
+		storage->shader_set_code(default_shader, "shader_type spatial; void vertex() { ROUGHNESS = 0.8; } void fragment() { ALBEDO=vec3(0.6); ROUGHNESS=0.8; METALLIC=0.2; } \n");
+		default_material = storage->material_allocate();
+		storage->material_initialize(default_material);
+		storage->material_set_shader(default_material, default_shader);
+
+		MaterialData *md = (MaterialData *)storage->material_get_data(default_material, RendererStorageRD::SHADER_TYPE_3D);
+		default_shader_rd = shader.version_get_shader(md->shader_data->version, SHADER_VERSION_COLOR_PASS);
+	}
+
+	{
+		overdraw_material_shader = storage->shader_allocate();
+		storage->shader_initialize(overdraw_material_shader);
+		storage->shader_set_code(overdraw_material_shader, "shader_type spatial;\nrender_mode blend_add,unshaded;\n void fragment() { ALBEDO=vec3(0.4,0.8,0.8); ALPHA=0.2; }");
+		overdraw_material = storage->material_allocate();
+		storage->material_initialize(overdraw_material);
+		storage->material_set_shader(overdraw_material, overdraw_material_shader);
+
+		wireframe_material_shader = storage->shader_allocate();
+		storage->shader_initialize(wireframe_material_shader);
+		storage->shader_set_code(wireframe_material_shader, "shader_type spatial;\nrender_mode wireframe,unshaded;\n void fragment() { ALBEDO=vec3(0.0,0.0,0.0); }");
+		wireframe_material = storage->material_allocate();
+		storage->material_initialize(wireframe_material);
+		storage->material_set_shader(wireframe_material, wireframe_material_shader);
+	}
+
+	{
+		default_vec4_xform_buffer = RD::get_singleton()->storage_buffer_create(256);
+		Vector<RD::Uniform> uniforms;
+		RD::Uniform u;
+		u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER;
+		u.ids.push_back(default_vec4_xform_buffer);
+		u.binding = 0;
+		uniforms.push_back(u);
+
+		default_vec4_xform_uniform_set = RD::get_singleton()->uniform_set_create(uniforms, default_shader_rd, RenderForwardMobile::TRANSFORMS_UNIFORM_SET);
+	}
+	{
+		RD::SamplerState sampler;
+		sampler.mag_filter = RD::SAMPLER_FILTER_LINEAR;
+		sampler.min_filter = RD::SAMPLER_FILTER_LINEAR;
+		sampler.enable_compare = true;
+		sampler.compare_op = RD::COMPARE_OP_LESS;
+		shadow_sampler = RD::get_singleton()->sampler_create(sampler);
+	}
+}
+
+SceneShaderForwardMobile::~SceneShaderForwardMobile() {
+	RD::get_singleton()->free(default_vec4_xform_buffer);
+	RD::get_singleton()->free(shadow_sampler);
+
+	storage->free(wireframe_material_shader);
+	storage->free(overdraw_material_shader);
+	storage->free(default_shader);
+
+	storage->free(wireframe_material);
+	storage->free(overdraw_material);
+	storage->free(default_material);
+}

+ 203 - 0
servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.h

@@ -0,0 +1,203 @@
+/*************************************************************************/
+/*  scene_shader_forward_mobile.h                                        */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md).   */
+/*                                                                       */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the       */
+/* "Software"), to deal in the Software without restriction, including   */
+/* without limitation the rights to use, copy, modify, merge, publish,   */
+/* distribute, sublicense, and/or sell copies of the Software, and to    */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions:                                             */
+/*                                                                       */
+/* The above copyright notice and this permission notice shall be        */
+/* included in all copies or substantial portions of the Software.       */
+/*                                                                       */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
+/*************************************************************************/
+
+#ifndef RSSR_SCENE_SHADER_FM_H
+#define RSSR_SCENE_SHADER_FM_H
+
+#include "servers/rendering/renderer_rd/renderer_scene_render_rd.h"
+#include "servers/rendering/renderer_rd/renderer_storage_rd.h"
+#include "servers/rendering/renderer_rd/shaders/scene_forward_mobile.glsl.gen.h"
+
+namespace RendererSceneRenderImplementation {
+
+class SceneShaderForwardMobile {
+private:
+	static SceneShaderForwardMobile *singleton;
+	RendererStorageRD *storage;
+
+public:
+	enum ShaderVersion {
+		SHADER_VERSION_COLOR_PASS,
+		SHADER_VERSION_LIGHTMAP_COLOR_PASS,
+		SHADER_VERSION_SHADOW_PASS,
+		SHADER_VERSION_DEPTH_PASS_DP,
+		SHADER_VERSION_DEPTH_PASS_WITH_MATERIAL,
+		SHADER_VERSION_MAX
+	};
+
+	struct ShaderData : public RendererStorageRD::ShaderData {
+		enum BlendMode { //used internally
+			BLEND_MODE_MIX,
+			BLEND_MODE_ADD,
+			BLEND_MODE_SUB,
+			BLEND_MODE_MUL,
+			BLEND_MODE_ALPHA_TO_COVERAGE
+		};
+
+		enum DepthDraw {
+			DEPTH_DRAW_DISABLED,
+			DEPTH_DRAW_OPAQUE,
+			DEPTH_DRAW_ALWAYS
+		};
+
+		enum DepthTest {
+			DEPTH_TEST_DISABLED,
+			DEPTH_TEST_ENABLED
+		};
+
+		enum Cull {
+			CULL_DISABLED,
+			CULL_FRONT,
+			CULL_BACK
+		};
+
+		enum CullVariant {
+			CULL_VARIANT_NORMAL,
+			CULL_VARIANT_REVERSED,
+			CULL_VARIANT_DOUBLE_SIDED,
+			CULL_VARIANT_MAX
+
+		};
+
+		enum AlphaAntiAliasing {
+			ALPHA_ANTIALIASING_OFF,
+			ALPHA_ANTIALIASING_ALPHA_TO_COVERAGE,
+			ALPHA_ANTIALIASING_ALPHA_TO_COVERAGE_AND_TO_ONE
+		};
+
+		bool valid;
+		RID version;
+		uint32_t vertex_input_mask;
+		PipelineCacheRD pipelines[CULL_VARIANT_MAX][RS::PRIMITIVE_MAX][SHADER_VERSION_MAX];
+
+		String path;
+
+		Map<StringName, ShaderLanguage::ShaderNode::Uniform> uniforms;
+		Vector<ShaderCompilerRD::GeneratedCode::Texture> texture_uniforms;
+
+		Vector<uint32_t> ubo_offsets;
+		uint32_t ubo_size;
+
+		String code;
+		Map<StringName, RID> default_texture_params;
+
+		DepthDraw depth_draw;
+		DepthTest depth_test;
+
+		bool uses_point_size;
+		bool uses_alpha;
+		bool uses_blend_alpha;
+		bool uses_alpha_clip;
+		bool uses_depth_pre_pass;
+		bool uses_discard;
+		bool uses_roughness;
+		bool uses_normal;
+		bool uses_particle_trails;
+
+		bool unshaded;
+		bool uses_vertex;
+		bool uses_sss;
+		bool uses_transmittance;
+		bool uses_screen_texture;
+		bool uses_depth_texture;
+		bool uses_normal_texture;
+		bool uses_time;
+		bool writes_modelview_or_projection;
+		bool uses_world_coordinates;
+
+		uint64_t last_pass = 0;
+		uint32_t index = 0;
+
+		virtual void set_code(const String &p_Code);
+		virtual void set_default_texture_param(const StringName &p_name, RID p_texture);
+		virtual void get_param_list(List<PropertyInfo> *p_param_list) const;
+		void get_instance_param_list(List<RendererStorage::InstanceShaderParam> *p_param_list) const;
+
+		virtual bool is_param_texture(const StringName &p_param) const;
+		virtual bool is_animated() const;
+		virtual bool casts_shadows() const;
+		virtual Variant get_default_parameter(const StringName &p_parameter) const;
+		virtual RS::ShaderNativeSourceCode get_native_source_code() const;
+
+		ShaderData();
+		virtual ~ShaderData();
+	};
+
+	RendererStorageRD::ShaderData *_create_shader_func();
+	static RendererStorageRD::ShaderData *_create_shader_funcs() {
+		return static_cast<SceneShaderForwardMobile *>(singleton)->_create_shader_func();
+	}
+
+	struct MaterialData : public RendererStorageRD::MaterialData {
+		uint64_t last_frame;
+		ShaderData *shader_data;
+		RID uniform_buffer;
+		RID uniform_set;
+		Vector<RID> texture_cache;
+		Vector<uint8_t> ubo_data;
+		uint64_t last_pass = 0;
+		uint32_t index = 0;
+		RID next_pass;
+		uint8_t priority;
+		virtual void set_render_priority(int p_priority);
+		virtual void set_next_pass(RID p_pass);
+		virtual void update_parameters(const Map<StringName, Variant> &p_parameters, bool p_uniform_dirty, bool p_textures_dirty);
+		virtual ~MaterialData();
+	};
+
+	RendererStorageRD::MaterialData *_create_material_func(ShaderData *p_shader);
+	static RendererStorageRD::MaterialData *_create_material_funcs(RendererStorageRD::ShaderData *p_shader) {
+		return static_cast<SceneShaderForwardMobile *>(singleton)->_create_material_func(static_cast<ShaderData *>(p_shader));
+	}
+
+	SceneForwardMobileShaderRD shader;
+	ShaderCompilerRD compiler;
+
+	RID default_shader;
+	RID default_material;
+	RID overdraw_material_shader;
+	RID overdraw_material;
+	RID wireframe_material_shader;
+	RID wireframe_material;
+	RID default_shader_rd;
+
+	RID default_vec4_xform_buffer;
+	RID default_vec4_xform_uniform_set;
+
+	RID shadow_sampler;
+
+	SceneShaderForwardMobile();
+	~SceneShaderForwardMobile();
+
+	void init(RendererStorageRD *p_storage, const String p_defines);
+};
+
+} // namespace RendererSceneRenderImplementation
+#endif // !RSSR_SCENE_SHADER_FM_H

+ 10 - 1
servers/rendering/renderer_rd/renderer_compositor_rd.cpp

@@ -175,5 +175,14 @@ RendererCompositorRD::RendererCompositorRD() {
 
 	storage = memnew(RendererStorageRD);
 	canvas = memnew(RendererCanvasRenderRD(storage));
-	scene = memnew(RendererSceneRenderImplementation::RenderForwardClustered(storage));
+
+	uint32_t back_end = GLOBAL_GET("rendering/vulkan/rendering/back_end");
+	uint32_t textures_per_stage = RD::get_singleton()->limit_get(RD::LIMIT_MAX_TEXTURES_PER_SHADER_STAGE);
+
+	if (back_end == 1 || textures_per_stage < 48) {
+		scene = memnew(RendererSceneRenderImplementation::RenderForwardMobile(storage));
+	} else { // back_end == 0
+		// default to our high end renderer
+		scene = memnew(RendererSceneRenderImplementation::RenderForwardClustered(storage));
+	}
 }

+ 1 - 0
servers/rendering/renderer_rd/renderer_compositor_rd.h

@@ -35,6 +35,7 @@
 #include "core/templates/thread_work_pool.h"
 #include "servers/rendering/renderer_compositor.h"
 #include "servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h"
+#include "servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h"
 #include "servers/rendering/renderer_rd/renderer_canvas_render_rd.h"
 #include "servers/rendering/renderer_rd/renderer_storage_rd.h"
 

+ 0 - 2
servers/rendering/renderer_rd/renderer_scene_gi_rd.cpp

@@ -3144,8 +3144,6 @@ void RendererSceneGIRD::process_gi(RID p_render_buffers, RID p_normal_roughness_
 		rb->reflection_buffer = RD::get_singleton()->texture_create(tf, RD::TextureView());
 		rb->ambient_buffer = RD::get_singleton()->texture_create(tf, RD::TextureView());
 		rb->gi.using_half_size_gi = half_resolution;
-
-		p_scene_render->_render_buffers_uniform_set_changed(p_render_buffers);
 	}
 
 	PushConstant push_constant;

+ 145 - 24
servers/rendering/renderer_rd/renderer_scene_render_rd.cpp

@@ -58,8 +58,6 @@ void RendererSceneRenderRD::sdfgi_update(RID p_render_buffers, RID p_environment
 			rb->sdfgi->erase();
 			memdelete(rb->sdfgi);
 			rb->sdfgi = nullptr;
-
-			_render_buffers_uniform_set_changed(p_render_buffers);
 		}
 		return;
 	}
@@ -78,8 +76,6 @@ void RendererSceneRenderRD::sdfgi_update(RID p_render_buffers, RID p_environment
 	if (sdfgi == nullptr) {
 		// re-create
 		rb->sdfgi = gi.create_sdfgi(env, p_world_position, requested_history_size);
-
-		_render_buffers_uniform_set_changed(p_render_buffers);
 	} else {
 		//check for updates
 		rb->sdfgi->update(env, p_world_position);
@@ -1533,7 +1529,6 @@ void RendererSceneRenderRD::_process_sss(RID p_render_buffers, const CameraMatri
 
 	if (rb->blur[0].texture.is_null()) {
 		_allocate_blur_textures(rb);
-		_render_buffers_uniform_set_changed(p_render_buffers);
 	}
 
 	storage->get_effects()->sub_surface_scattering(rb->texture, rb->blur[0].mipmaps[0].texture, rb->depth_texture, p_camera, Size2i(rb->width, rb->height), sss_scale, sss_depth_scale, sss_quality);
@@ -1585,7 +1580,6 @@ void RendererSceneRenderRD::_process_ssr(RID p_render_buffers, RID p_dest_frameb
 
 	if (rb->blur[0].texture.is_null()) {
 		_allocate_blur_textures(rb);
-		_render_buffers_uniform_set_changed(p_render_buffers);
 	}
 
 	storage->get_effects()->screen_space_reflection(rb->texture, p_normal_buffer, ssr_roughness_quality, rb->ssr.blur_radius[0], rb->ssr.blur_radius[1], p_metallic, p_metallic_mask, rb->depth_texture, rb->ssr.depth_scaled, rb->ssr.normal_scaled, rb->blur[0].mipmaps[1].texture, rb->blur[1].mipmaps[0].texture, Size2i(rb->width / 2, rb->height / 2), env->ssr_max_steps, env->ssr_fade_in, env->ssr_fade_out, env->ssr_depth_tolerance, p_projection);
@@ -1711,7 +1705,6 @@ void RendererSceneRenderRD::_process_ssao(RID p_render_buffers, RID p_environmen
 			tf.usage_bits = RD::TEXTURE_USAGE_SAMPLING_BIT | RD::TEXTURE_USAGE_STORAGE_BIT;
 			rb->ssao.ao_final = RD::get_singleton()->texture_create(tf, RD::TextureView());
 			RD::get_singleton()->set_resource_name(rb->ssao.ao_final, "SSAO Final");
-			_render_buffers_uniform_set_changed(p_render_buffers);
 		}
 		ssao_using_half_size = ssao_half_size;
 		uniform_sets_are_invalid = true;
@@ -1751,7 +1744,6 @@ void RendererSceneRenderRD::_render_buffers_post_process_and_tonemap(RID p_rende
 	if (can_use_effects && camfx && (camfx->dof_blur_near_enabled || camfx->dof_blur_far_enabled) && camfx->dof_blur_amount > 0.0) {
 		if (rb->blur[0].texture.is_null()) {
 			_allocate_blur_textures(rb);
-			_render_buffers_uniform_set_changed(p_render_buffers);
 		}
 
 		float bokeh_size = camfx->dof_blur_amount * 64.0;
@@ -1761,7 +1753,6 @@ void RendererSceneRenderRD::_render_buffers_post_process_and_tonemap(RID p_rende
 	if (can_use_effects && env && env->auto_exposure) {
 		if (rb->luminance.current.is_null()) {
 			_allocate_luminance_textures(rb);
-			_render_buffers_uniform_set_changed(p_render_buffers);
 		}
 
 		bool set_immediate = env->auto_exposure_version != rb->auto_exposure_version;
@@ -1782,7 +1773,6 @@ void RendererSceneRenderRD::_render_buffers_post_process_and_tonemap(RID p_rende
 
 		if (rb->blur[1].texture.is_null()) {
 			_allocate_blur_textures(rb);
-			_render_buffers_uniform_set_changed(p_render_buffers);
 		}
 
 		for (int i = 0; i < RS::MAX_GLOW_LEVELS; i++) {
@@ -2177,7 +2167,6 @@ void RendererSceneRenderRD::render_buffers_configure(RID p_render_buffers, RID p
 	}
 
 	rb->data->configure(rb->texture, rb->depth_texture, p_width, p_height, p_msaa);
-	_render_buffers_uniform_set_changed(p_render_buffers);
 
 	if (is_clustered_enabled()) {
 		rb->cluster_builder->setup(Size2i(p_width, p_height), max_cluster_elements, rb->depth_texture, storage->sampler_rd_get_default(RS::CANVAS_ITEM_TEXTURE_FILTER_NEAREST, RS::CANVAS_ITEM_TEXTURE_REPEAT_DISABLED), rb->texture);
@@ -2329,6 +2318,8 @@ void RendererSceneRenderRD::_setup_reflections(const PagedArray<RID> &p_reflecti
 
 		Vector3 extents = storage->reflection_probe_get_extents(base_probe);
 
+		rpi->cull_mask = storage->reflection_probe_get_cull_mask(base_probe);
+
 		reflection_ubo.box_extents[0] = extents.x;
 		reflection_ubo.box_extents[1] = extents.y;
 		reflection_ubo.box_extents[2] = extents.z;
@@ -2357,7 +2348,9 @@ void RendererSceneRenderRD::_setup_reflections(const PagedArray<RID> &p_reflecti
 		Transform proj = (p_camera_inverse_transform * transform).inverse();
 		RendererStorageRD::store_transform(proj, reflection_ubo.local_matrix);
 
-		current_cluster_builder->add_box(ClusterBuilderRD::BOX_TYPE_REFLECTION_PROBE, transform, extents);
+		if (current_cluster_builder != nullptr) {
+			current_cluster_builder->add_box(ClusterBuilderRD::BOX_TYPE_REFLECTION_PROBE, transform, extents);
+		}
 
 		rpi->last_pass = RSG::rasterizer->get_frame_number();
 	}
@@ -2747,8 +2740,11 @@ void RendererSceneRenderRD::_setup_lights(const PagedArray<RID> &p_lights, const
 		}
 
 		li->light_index = index;
+		li->cull_mask = storage->light_get_cull_mask(base);
 
-		current_cluster_builder->add_light(type == RS::LIGHT_SPOT ? ClusterBuilderRD::LIGHT_TYPE_SPOT : ClusterBuilderRD::LIGHT_TYPE_OMNI, light_transform, radius, spot_angle);
+		if (current_cluster_builder != nullptr) {
+			current_cluster_builder->add_light(type == RS::LIGHT_SPOT ? ClusterBuilderRD::LIGHT_TYPE_SPOT : ClusterBuilderRD::LIGHT_TYPE_OMNI, light_transform, radius, spot_angle);
+		}
 
 		r_positional_light_count++;
 	}
@@ -2816,6 +2812,9 @@ void RendererSceneRenderRD::_setup_decals(const PagedArray<RID> &p_decals, const
 		DecalInstance *di = cluster.decal_sort[i].instance;
 		RID decal = di->decal;
 
+		di->render_index = i;
+		di->cull_mask = storage->decal_get_cull_mask(decal);
+
 		Transform xform = di->transform;
 		float fade = 1.0;
 
@@ -2920,7 +2919,9 @@ void RendererSceneRenderRD::_setup_decals(const PagedArray<RID> &p_decals, const
 		dd.upper_fade = storage->decal_get_upper_fade(decal);
 		dd.lower_fade = storage->decal_get_lower_fade(decal);
 
-		current_cluster_builder->add_box(ClusterBuilderRD::BOX_TYPE_DECAL, xform, decal_extents);
+		if (current_cluster_builder != nullptr) {
+			current_cluster_builder->add_box(ClusterBuilderRD::BOX_TYPE_DECAL, xform, decal_extents);
+		}
 	}
 
 	if (cluster.decal_count > 0) {
@@ -2928,6 +2929,116 @@ void RendererSceneRenderRD::_setup_decals(const PagedArray<RID> &p_decals, const
 	}
 }
 
+void RendererSceneRenderRD::_fill_instance_indices(const RID *p_omni_light_instances, uint32_t p_omni_light_instance_count, uint32_t *p_omni_light_indices, const RID *p_spot_light_instances, uint32_t p_spot_light_instance_count, uint32_t *p_spot_light_indices, const RID *p_reflection_probe_instances, uint32_t p_reflection_probe_instance_count, uint32_t *p_reflection_probe_indices, const RID *p_decal_instances, uint32_t p_decal_instance_count, uint32_t *p_decal_instance_indices, uint32_t p_layer_mask, uint32_t p_max_dst_words) {
+	// first zero out our indices
+	for (uint32_t i = 0; i < p_max_dst_words; i++) {
+		p_omni_light_indices[i] = 0;
+		p_spot_light_indices[i] = 0;
+		p_reflection_probe_indices[i] = 0;
+		p_decal_instance_indices[i] = 0;
+	}
+
+	{
+		// process omni lights
+		uint32_t dword = 0;
+		uint32_t shift = 0;
+
+		for (uint32_t i = 0; i < p_omni_light_instance_count && dword < p_max_dst_words; i++) {
+			LightInstance *li = light_instance_owner.getornull(p_omni_light_instances[i]);
+
+			if ((li->cull_mask & p_layer_mask) && (li->light_index < 255)) {
+				p_omni_light_indices[dword] += li->light_index << shift;
+				if (shift == 24) {
+					dword++;
+					shift = 0;
+				} else {
+					shift += 8;
+				}
+			}
+		}
+
+		if (dword < 2) {
+			// put in ending mark
+			p_omni_light_indices[dword] += 0xFF << shift;
+		}
+	}
+
+	{
+		// process spot lights
+		uint32_t dword = 0;
+		uint32_t shift = 0;
+
+		for (uint32_t i = 0; i < p_spot_light_instance_count && dword < p_max_dst_words; i++) {
+			LightInstance *li = light_instance_owner.getornull(p_spot_light_instances[i]);
+
+			if ((li->cull_mask & p_layer_mask) && (li->light_index < 255)) {
+				p_spot_light_indices[dword] += li->light_index << shift;
+				if (shift == 24) {
+					dword++;
+					shift = 0;
+				} else {
+					shift += 8;
+				}
+			}
+		}
+
+		if (dword < 2) {
+			// put in ending mark
+			p_spot_light_indices[dword] += 0xFF << shift;
+		}
+	}
+
+	{
+		// process reflection probes
+		uint32_t dword = 0;
+		uint32_t shift = 0;
+
+		for (uint32_t i = 0; i < p_reflection_probe_instance_count && dword < p_max_dst_words; i++) {
+			ReflectionProbeInstance *rpi = reflection_probe_instance_owner.getornull(p_reflection_probe_instances[i]);
+
+			if ((rpi->cull_mask & p_layer_mask) && (rpi->render_index < 255)) {
+				p_reflection_probe_indices[dword] += rpi->render_index << shift;
+				if (shift == 24) {
+					dword++;
+					shift = 0;
+				} else {
+					shift += 8;
+				}
+			}
+		}
+
+		if (dword < 2) {
+			// put in ending mark
+			p_reflection_probe_indices[dword] += 0xFF << shift;
+		}
+	}
+
+	{
+		// process decals
+		uint32_t dword = 0;
+		uint32_t shift = 0;
+
+		for (uint32_t i = 0; i < p_decal_instance_count && dword < p_max_dst_words; i++) {
+			DecalInstance *decal = decal_instance_owner.getornull(p_decal_instances[i]);
+
+			if ((decal->cull_mask & p_layer_mask) && (decal->render_index < 255)) {
+				p_decal_instance_indices[dword] += decal->render_index << shift;
+				if (shift == 24) {
+					dword++;
+					shift = 0;
+				} else {
+					shift += 8;
+				}
+			}
+		}
+
+		if (dword < 2) {
+			// put in ending mark
+			p_decal_instance_indices[dword] += 0xFF << shift;
+		}
+	}
+}
+
 void RendererSceneRenderRD::_volumetric_fog_erase(RenderBuffers *rb) {
 	ERR_FAIL_COND(!rb->volumetric_fog);
 
@@ -2967,7 +3078,6 @@ void RendererSceneRenderRD::_update_volumetric_fog(RID p_render_buffers, RID p_e
 		//validate
 		if (!env || !env->volumetric_fog_enabled || rb->volumetric_fog->width != target_width || rb->volumetric_fog->height != target_height || rb->volumetric_fog->depth != volumetric_fog_depth) {
 			_volumetric_fog_erase(rb);
-			_render_buffers_uniform_set_changed(p_render_buffers);
 		}
 	}
 
@@ -3003,7 +3113,6 @@ void RendererSceneRenderRD::_update_volumetric_fog(RID p_render_buffers, RID p_e
 		tf.usage_bits = RD::TEXTURE_USAGE_STORAGE_BIT | RD::TEXTURE_USAGE_SAMPLING_BIT;
 
 		rb->volumetric_fog->fog_map = RD::get_singleton()->texture_create(tf, RD::TextureView());
-		_render_buffers_uniform_set_changed(p_render_buffers);
 
 		Vector<RD::Uniform> uniforms;
 		{
@@ -3528,7 +3637,7 @@ void RendererSceneRenderRD::render_scene(RID p_render_buffers, const Transform &
 	RenderBuffers *rb = nullptr;
 	if (p_render_buffers.is_valid()) {
 		rb = render_buffers_owner.getornull(p_render_buffers);
-		ERR_FAIL_COND(!rb); // !BAS! Do we fail here or skip the parts that won't work. can't really see a case why we would be rendering without buffers....
+		ERR_FAIL_COND(!rb);
 	}
 
 	//assign render data
@@ -3584,10 +3693,12 @@ void RendererSceneRenderRD::render_scene(RID p_render_buffers, const Transform &
 	}
 
 	//assign render indices to giprobes
-	for (uint32_t i = 0; i < (uint32_t)p_gi_probes.size(); i++) {
-		RendererSceneGIRD::GIProbeInstance *giprobe_inst = gi.gi_probe_instance_owner.getornull(p_gi_probes[i]);
-		if (giprobe_inst) {
-			giprobe_inst->render_index = i;
+	if (is_dynamic_gi_supported()) {
+		for (uint32_t i = 0; i < (uint32_t)p_gi_probes.size(); i++) {
+			RendererSceneGIRD::GIProbeInstance *giprobe_inst = gi.gi_probe_instance_owner.getornull(p_gi_probes[i]);
+			if (giprobe_inst) {
+				giprobe_inst->render_index = i;
+			}
 		}
 	}
 
@@ -3623,7 +3734,11 @@ void RendererSceneRenderRD::render_scene(RID p_render_buffers, const Transform &
 
 	render_state.depth_prepass_used = false;
 	//calls _pre_opaque_render between depth pre-pass and opaque pass
-	_render_scene(p_render_buffers, p_cam_transform, p_cam_projection, p_cam_ortogonal, p_instances, *render_state.gi_probes, p_lightmaps, p_environment, current_cluster_builder->get_cluster_buffer(), current_cluster_builder->get_cluster_size(), current_cluster_builder->get_max_cluster_elements(), p_camera_effects, p_shadow_atlas, p_reflection_atlas, p_reflection_probe, p_reflection_probe_pass, clear_color, p_screen_lod_threshold);
+	if (current_cluster_builder != nullptr) {
+		_render_scene(p_render_buffers, p_cam_transform, p_cam_projection, p_cam_ortogonal, p_instances, *render_state.gi_probes, p_lightmaps, p_environment, current_cluster_builder->get_cluster_buffer(), current_cluster_builder->get_cluster_size(), current_cluster_builder->get_max_cluster_elements(), p_camera_effects, p_shadow_atlas, p_reflection_atlas, p_reflection_probe, p_reflection_probe_pass, clear_color, p_screen_lod_threshold);
+	} else {
+		_render_scene(p_render_buffers, p_cam_transform, p_cam_projection, p_cam_ortogonal, p_instances, *render_state.gi_probes, p_lightmaps, p_environment, RID(), 0, 0, p_camera_effects, p_shadow_atlas, p_reflection_atlas, p_reflection_probe, p_reflection_probe_pass, clear_color, p_screen_lod_threshold);
+	}
 
 	if (p_render_buffers.is_valid()) {
 		if (debug_draw == RS::VIEWPORT_DEBUG_DRAW_CLUSTER_OMNI_LIGHTS || debug_draw == RS::VIEWPORT_DEBUG_DRAW_CLUSTER_SPOT_LIGHTS || debug_draw == RS::VIEWPORT_DEBUG_DRAW_CLUSTER_DECALS || debug_draw == RS::VIEWPORT_DEBUG_DRAW_CLUSTER_REFLECTION_PROBES) {
@@ -3644,7 +3759,9 @@ void RendererSceneRenderRD::render_scene(RID p_render_buffers, const Transform &
 				default: {
 				}
 			}
-			current_cluster_builder->debug(elem_type);
+			if (current_cluster_builder != nullptr) {
+				current_cluster_builder->debug(elem_type);
+			}
 		}
 
 		RENDER_TIMESTAMP("Tonemap");
@@ -4105,8 +4222,12 @@ bool RendererSceneRenderRD::is_volumetric_supported() const {
 	return true;
 }
 
+uint32_t RendererSceneRenderRD::get_max_elements() const {
+	return GLOBAL_GET("rendering/limits/cluster_builder/max_clustered_elements");
+}
+
 RendererSceneRenderRD::RendererSceneRenderRD(RendererStorageRD *p_storage) {
-	max_cluster_elements = GLOBAL_GET("rendering/limits/cluster_builder/max_clustered_elements");
+	max_cluster_elements = get_max_elements();
 
 	storage = p_storage;
 	singleton = this;

+ 9 - 1
servers/rendering/renderer_rd/renderer_scene_render_rd.h

@@ -79,7 +79,6 @@ protected:
 	RenderBufferData *render_buffers_get_data(RID p_render_buffers);
 
 	virtual void _base_uniforms_changed() = 0;
-	virtual void _render_buffers_uniform_set_changed(RID p_render_buffers) = 0;
 	virtual RID _render_buffers_get_normal_texture(RID p_render_buffers) = 0;
 
 	void _process_ssao(RID p_render_buffers, RID p_environment, RID p_normal_buffer, const CameraMatrix &p_projection);
@@ -150,6 +149,7 @@ private:
 		uint32_t render_step = 0;
 		uint64_t last_pass = 0;
 		uint32_t render_index = 0;
+		uint32_t cull_mask = 0;
 
 		Transform transform;
 	};
@@ -161,6 +161,8 @@ private:
 	struct DecalInstance {
 		RID decal;
 		Transform transform;
+		uint32_t render_index;
+		uint32_t cull_mask;
 	};
 
 	mutable RID_Owner<DecalInstance> decal_instance_owner;
@@ -305,6 +307,7 @@ private:
 		uint64_t last_scene_shadow_pass = 0;
 		uint64_t last_pass = 0;
 		uint32_t light_index = 0;
+		uint32_t cull_mask = 0;
 		uint32_t light_directional_index = 0;
 
 		uint32_t current_shadow_atlas_key = 0;
@@ -449,6 +452,8 @@ private:
 	struct Cluster {
 		/* Scene State UBO */
 
+		// !BAS! Most data here is not just used by our clustering logic but also by other lighting implementations. Maybe rename this struct to something more appropriate
+
 		enum {
 			REFLECTION_AMBIENT_DISABLED = 0,
 			REFLECTION_AMBIENT_ENVIRONMENT = 1,
@@ -1085,6 +1090,8 @@ public:
 		return li->transform;
 	}
 
+	void _fill_instance_indices(const RID *p_omni_light_instances, uint32_t p_omni_light_instance_count, uint32_t *p_omni_light_indices, const RID *p_spot_light_instances, uint32_t p_spot_light_instance_count, uint32_t *p_spot_light_indices, const RID *p_reflection_probe_instances, uint32_t p_reflection_probe_instance_count, uint32_t *p_reflection_probe_indices, const RID *p_decal_instances, uint32_t p_decal_instance_count, uint32_t *p_decal_instance_indices, uint32_t p_layer_mask, uint32_t p_max_dst_words = 2);
+
 	/* gi light probes */
 
 	RID gi_probe_instance_create(RID p_base);
@@ -1192,6 +1199,7 @@ public:
 	virtual bool is_dynamic_gi_supported() const;
 	virtual bool is_clustered_enabled() const;
 	virtual bool is_volumetric_supported() const;
+	virtual uint32_t get_max_elements() const;
 
 	RendererSceneRenderRD(RendererStorageRD *p_storage);
 	~RendererSceneRenderRD();

+ 0 - 102
servers/rendering/renderer_rd/shaders/cluster_data_inc.glsl

@@ -1,105 +1,3 @@
-
 #define CLUSTER_COUNTER_SHIFT 20
 #define CLUSTER_POINTER_MASK ((1 << CLUSTER_COUNTER_SHIFT) - 1)
 #define CLUSTER_COUNTER_MASK 0xfff
-
-struct LightData { //this structure needs to be as packed as possible
-	vec3 position;
-	float inv_radius;
-
-	vec3 direction;
-	float size;
-
-	vec3 color;
-	float attenuation;
-
-	float cone_attenuation;
-	float cone_angle;
-	float specular_amount;
-	bool shadow_enabled;
-
-	vec4 atlas_rect; // rect in the shadow atlas
-	mat4 shadow_matrix;
-	float shadow_bias;
-	float shadow_normal_bias;
-	float transmittance_bias;
-	float soft_shadow_size; // for spot, it's the size in uv coordinates of the light, for omni it's the span angle
-	float soft_shadow_scale; // scales the shadow kernel for blurrier shadows
-	uint mask;
-	float shadow_volumetric_fog_fade;
-	uint pad;
-	vec4 projector_rect; //projector rect in srgb decal atlas
-};
-
-#define REFLECTION_AMBIENT_DISABLED 0
-#define REFLECTION_AMBIENT_ENVIRONMENT 1
-#define REFLECTION_AMBIENT_COLOR 2
-
-struct ReflectionData {
-	vec3 box_extents;
-	float index;
-	vec3 box_offset;
-	uint mask;
-	vec3 ambient; // ambient color
-	float intensity;
-	bool exterior;
-	bool box_project;
-	uint ambient_mode;
-	uint pad;
-	//0-8 is intensity,8-9 is ambient, mode
-	mat4 local_matrix; // up to here for spot and omni, rest is for directional
-	// notes: for ambientblend, use distance to edge to blend between already existing global environment
-};
-
-struct DirectionalLightData {
-	vec3 direction;
-	float energy;
-	vec3 color;
-	float size;
-	float specular;
-	uint mask;
-	float softshadow_angle;
-	float soft_shadow_scale;
-	bool blend_splits;
-	bool shadow_enabled;
-	float fade_from;
-	float fade_to;
-	uvec3 pad;
-	float shadow_volumetric_fog_fade;
-	vec4 shadow_bias;
-	vec4 shadow_normal_bias;
-	vec4 shadow_transmittance_bias;
-	vec4 shadow_z_range;
-	vec4 shadow_range_begin;
-	vec4 shadow_split_offsets;
-	mat4 shadow_matrix1;
-	mat4 shadow_matrix2;
-	mat4 shadow_matrix3;
-	mat4 shadow_matrix4;
-	vec4 shadow_color1;
-	vec4 shadow_color2;
-	vec4 shadow_color3;
-	vec4 shadow_color4;
-	vec2 uv_scale1;
-	vec2 uv_scale2;
-	vec2 uv_scale3;
-	vec2 uv_scale4;
-};
-
-struct DecalData {
-	mat4 xform; //to decal transform
-	vec3 inv_extents;
-	float albedo_mix;
-	vec4 albedo_rect;
-	vec4 normal_rect;
-	vec4 orm_rect;
-	vec4 emission_rect;
-	vec4 modulate;
-	float emission_energy;
-	uint mask;
-	float upper_fade;
-	float lower_fade;
-	mat3x4 normal_xform;
-	vec3 normal;
-	float normal_fade;
-};

+ 18 - 0
servers/rendering/renderer_rd/shaders/decal_data_inc.glsl

@@ -0,0 +1,18 @@
+
+struct DecalData {
+	mat4 xform; //to decal transform
+	vec3 inv_extents;
+	float albedo_mix;
+	vec4 albedo_rect;
+	vec4 normal_rect;
+	vec4 orm_rect;
+	vec4 emission_rect;
+	vec4 modulate;
+	float emission_energy;
+	uint mask;
+	float upper_fade;
+	float lower_fade;
+	mat3x4 normal_xform;
+	vec3 normal;
+	float normal_fade;
+};

+ 83 - 0
servers/rendering/renderer_rd/shaders/light_data_inc.glsl

@@ -0,0 +1,83 @@
+
+struct LightData { //this structure needs to be as packed as possible
+	vec3 position;
+	float inv_radius;
+
+	vec3 direction;
+	float size;
+
+	vec3 color;
+	float attenuation;
+
+	float cone_attenuation;
+	float cone_angle;
+	float specular_amount;
+	bool shadow_enabled;
+
+	vec4 atlas_rect; // rect in the shadow atlas
+	mat4 shadow_matrix;
+	float shadow_bias;
+	float shadow_normal_bias;
+	float transmittance_bias;
+	float soft_shadow_size; // for spot, it's the size in uv coordinates of the light, for omni it's the span angle
+	float soft_shadow_scale; // scales the shadow kernel for blurrier shadows
+	uint mask;
+	float shadow_volumetric_fog_fade;
+	uint pad;
+	vec4 projector_rect; //projector rect in srgb decal atlas
+};
+
+#define REFLECTION_AMBIENT_DISABLED 0
+#define REFLECTION_AMBIENT_ENVIRONMENT 1
+#define REFLECTION_AMBIENT_COLOR 2
+
+struct ReflectionData {
+	vec3 box_extents;
+	float index;
+	vec3 box_offset;
+	uint mask;
+	vec3 ambient; // ambient color
+	float intensity;
+	bool exterior;
+	bool box_project;
+	uint ambient_mode;
+	uint pad;
+	//0-8 is intensity,8-9 is ambient, mode
+	mat4 local_matrix; // up to here for spot and omni, rest is for directional
+	// notes: for ambientblend, use distance to edge to blend between already existing global environment
+};
+
+struct DirectionalLightData {
+	vec3 direction;
+	float energy;
+	vec3 color;
+	float size;
+	float specular;
+	uint mask;
+	float softshadow_angle;
+	float soft_shadow_scale;
+	bool blend_splits;
+	bool shadow_enabled;
+	float fade_from;
+	float fade_to;
+	uvec3 pad;
+	float shadow_volumetric_fog_fade;
+	vec4 shadow_bias;
+	vec4 shadow_normal_bias;
+	vec4 shadow_transmittance_bias;
+	vec4 shadow_z_range;
+	vec4 shadow_range_begin;
+	vec4 shadow_split_offsets;
+	mat4 shadow_matrix1;
+	mat4 shadow_matrix2;
+	mat4 shadow_matrix3;
+	mat4 shadow_matrix4;
+	vec4 shadow_color1;
+	vec4 shadow_color2;
+	vec4 shadow_color3;
+	vec4 shadow_color4;
+	vec2 uv_scale1;
+	vec2 uv_scale2;
+	vec2 uv_scale3;
+	vec2 uv_scale4;
+};

+ 58 - 0
servers/rendering/renderer_rd/shaders/scene_forward_aa_inc.glsl

@@ -0,0 +1,58 @@
+#ifdef ALPHA_HASH_USED
+
+float hash_2d(vec2 p) {
+	return fract(1.0e4 * sin(17.0 * p.x + 0.1 * p.y) *
+				 (0.1 + abs(sin(13.0 * p.y + p.x))));
+}
+
+float hash_3d(vec3 p) {
+	return hash_2d(vec2(hash_2d(p.xy), p.z));
+}
+
+float compute_alpha_hash_threshold(vec3 pos, float hash_scale) {
+	vec3 dx = dFdx(pos);
+	vec3 dy = dFdx(pos);
+	float delta_max_sqr = max(length(dx), length(dy));
+	float pix_scale = 1.0 / (hash_scale * delta_max_sqr);
+
+	vec2 pix_scales =
+			vec2(exp2(floor(log2(pix_scale))), exp2(ceil(log2(pix_scale))));
+
+	vec2 a_thresh = vec2(hash_3d(floor(pix_scales.x * pos.xyz)),
+			hash_3d(floor(pix_scales.y * pos.xyz)));
+
+	float lerp_factor = fract(log2(pix_scale));
+
+	float a_interp = (1.0 - lerp_factor) * a_thresh.x + lerp_factor * a_thresh.y;
+
+	float min_lerp = min(lerp_factor, 1.0 - lerp_factor);
+
+	vec3 cases = vec3(a_interp * a_interp / (2.0 * min_lerp * (1.0 - min_lerp)),
+			(a_interp - 0.5 * min_lerp) / (1.0 - min_lerp),
+			1.0 - ((1.0 - a_interp) * (1.0 - a_interp) /
+						  (2.0 * min_lerp * (1.0 - min_lerp))));
+
+	float alpha_hash_threshold =
+			(lerp_factor < (1.0 - min_lerp)) ? ((lerp_factor < min_lerp) ? cases.x : cases.y) : cases.z;
+
+	return clamp(alpha_hash_threshold, 0.0, 1.0);
+}
+
+#endif // ALPHA_HASH_USED
+
+#ifdef ALPHA_ANTIALIASING_EDGE_USED
+
+float calc_mip_level(vec2 texture_coord) {
+	vec2 dx = dFdx(texture_coord);
+	vec2 dy = dFdy(texture_coord);
+	float delta_max_sqr = max(dot(dx, dx), dot(dy, dy));
+	return max(0.0, 0.5 * log2(delta_max_sqr));
+}
+
+float compute_alpha_antialiasing_edge(float input_alpha, vec2 texture_coord, float alpha_edge) {
+	input_alpha *= 1.0 + max(0, calc_mip_level(texture_coord)) * 0.25; // 0.25 mip scale, magic number
+	input_alpha = (input_alpha - alpha_edge) / max(fwidth(input_alpha), 0.0001) + 0.5;
+	return clamp(input_alpha, 0.0, 1.0);
+}
+
+#endif // ALPHA_ANTIALIASING_USED

File diff suppressed because it is too large
+ 7 - 1303
servers/rendering/renderer_rd/shaders/scene_forward_clustered.glsl


+ 7 - 7
servers/rendering/renderer_rd/shaders/scene_forward_clustered_inc.glsl

@@ -13,6 +13,7 @@
 #endif
 
 #include "cluster_data_inc.glsl"
+#include "decal_data_inc.glsl"
 
 #if !defined(MODE_RENDER_DEPTH) || defined(MODE_RENDER_MATERIAL) || defined(MODE_RENDER_SDF) || defined(MODE_RENDER_NORMAL_ROUGHNESS) || defined(MODE_RENDER_GIPROBE) || defined(TANGENT_USED) || defined(NORMAL_MAP_USED)
 #ifndef NORMAL_USED
@@ -28,7 +29,11 @@ layout(push_constant, binding = 0, std430) uniform DrawCall {
 }
 draw_call;
 
-/* Set 0 Scene data that never changes, ever */
+#define SDFGI_MAX_CASCADES 8
+
+/* Set 0: Base Pass (never changes) */
+
+#include "light_data_inc.glsl"
 
 #define SAMPLER_NEAREST_CLAMP 0
 #define SAMPLER_LINEAR_CLAMP 1
@@ -43,10 +48,6 @@ draw_call;
 #define SAMPLER_NEAREST_WITH_MIPMAPS_ANISOTROPIC_REPEAT 10
 #define SAMPLER_LINEAR_WITH_MIPMAPS_ANISOTROPIC_REPEAT 11
 
-#define SDFGI_MAX_CASCADES 8
-
-/* Set 1: Base Pass (never changes) */
-
 layout(set = 0, binding = 1) uniform sampler material_samplers[12];
 
 layout(set = 0, binding = 2) uniform sampler shadow_sampler;
@@ -156,7 +157,7 @@ layout(set = 0, binding = 13, std140) uniform SDFGI {
 }
 sdfgi;
 
-/* Set 2: Render Pass (changes per render pass) */
+/* Set 1: Render Pass (changes per render pass) */
 
 layout(set = 1, binding = 0, std140) uniform SceneData {
 	mat4 projection_matrix;
@@ -240,7 +241,6 @@ layout(set = 1, binding = 0, std140) uniform SceneData {
 
 	bool pancake_shadows;
 }
-
 scene_data;
 
 struct InstanceData {

+ 242 - 0
servers/rendering/renderer_rd/shaders/scene_forward_gi_inc.glsl

@@ -0,0 +1,242 @@
+// Functions related to gi/sdfgi for our forward renderer
+
+//standard voxel cone trace
+vec4 voxel_cone_trace(texture3D probe, vec3 cell_size, vec3 pos, vec3 direction, float tan_half_angle, float max_distance, float p_bias) {
+	float dist = p_bias;
+	vec4 color = vec4(0.0);
+
+	while (dist < max_distance && color.a < 0.95) {
+		float diameter = max(1.0, 2.0 * tan_half_angle * dist);
+		vec3 uvw_pos = (pos + dist * direction) * cell_size;
+		float half_diameter = diameter * 0.5;
+		//check if outside, then break
+		if (any(greaterThan(abs(uvw_pos - 0.5), vec3(0.5f + half_diameter * cell_size)))) {
+			break;
+		}
+		vec4 scolor = textureLod(sampler3D(probe, material_samplers[SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP]), uvw_pos, log2(diameter));
+		float a = (1.0 - color.a);
+		color += a * scolor;
+		dist += half_diameter;
+	}
+
+	return color;
+}
+
+vec4 voxel_cone_trace_45_degrees(texture3D probe, vec3 cell_size, vec3 pos, vec3 direction, float tan_half_angle, float max_distance, float p_bias) {
+	float dist = p_bias;
+	vec4 color = vec4(0.0);
+	float radius = max(0.5, tan_half_angle * dist);
+	float lod_level = log2(radius * 2.0);
+
+	while (dist < max_distance && color.a < 0.95) {
+		vec3 uvw_pos = (pos + dist * direction) * cell_size;
+
+		//check if outside, then break
+		if (any(greaterThan(abs(uvw_pos - 0.5), vec3(0.5f + radius * cell_size)))) {
+			break;
+		}
+		vec4 scolor = textureLod(sampler3D(probe, material_samplers[SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP]), uvw_pos, lod_level);
+		lod_level += 1.0;
+
+		float a = (1.0 - color.a);
+		scolor *= a;
+		color += scolor;
+		dist += radius;
+		radius = max(0.5, tan_half_angle * dist);
+	}
+
+	return color;
+}
+
+void gi_probe_compute(uint index, vec3 position, vec3 normal, vec3 ref_vec, mat3 normal_xform, float roughness, vec3 ambient, vec3 environment, inout vec4 out_spec, inout vec4 out_diff) {
+	position = (gi_probes.data[index].xform * vec4(position, 1.0)).xyz;
+	ref_vec = normalize((gi_probes.data[index].xform * vec4(ref_vec, 0.0)).xyz);
+	normal = normalize((gi_probes.data[index].xform * vec4(normal, 0.0)).xyz);
+
+	position += normal * gi_probes.data[index].normal_bias;
+
+	//this causes corrupted pixels, i have no idea why..
+	if (any(bvec2(any(lessThan(position, vec3(0.0))), any(greaterThan(position, gi_probes.data[index].bounds))))) {
+		return;
+	}
+
+	vec3 blendv = abs(position / gi_probes.data[index].bounds * 2.0 - 1.0);
+	float blend = clamp(1.0 - max(blendv.x, max(blendv.y, blendv.z)), 0.0, 1.0);
+	//float blend=1.0;
+
+	float max_distance = length(gi_probes.data[index].bounds);
+	vec3 cell_size = 1.0 / gi_probes.data[index].bounds;
+
+	//radiance
+
+#define MAX_CONE_DIRS 4
+
+	vec3 cone_dirs[MAX_CONE_DIRS] = vec3[](
+			vec3(0.707107, 0.0, 0.707107),
+			vec3(0.0, 0.707107, 0.707107),
+			vec3(-0.707107, 0.0, 0.707107),
+			vec3(0.0, -0.707107, 0.707107));
+
+	float cone_weights[MAX_CONE_DIRS] = float[](0.25, 0.25, 0.25, 0.25);
+	float cone_angle_tan = 0.98269;
+
+	vec3 light = vec3(0.0);
+
+	for (int i = 0; i < MAX_CONE_DIRS; i++) {
+		vec3 dir = normalize((gi_probes.data[index].xform * vec4(normal_xform * cone_dirs[i], 0.0)).xyz);
+
+		vec4 cone_light = voxel_cone_trace_45_degrees(gi_probe_textures[index], cell_size, position, dir, cone_angle_tan, max_distance, gi_probes.data[index].bias);
+
+		if (gi_probes.data[index].blend_ambient) {
+			cone_light.rgb = mix(ambient, cone_light.rgb, min(1.0, cone_light.a / 0.95));
+		}
+
+		light += cone_weights[i] * cone_light.rgb;
+	}
+
+	light *= gi_probes.data[index].dynamic_range;
+	out_diff += vec4(light * blend, blend);
+
+	//irradiance
+	vec4 irr_light = voxel_cone_trace(gi_probe_textures[index], cell_size, position, ref_vec, tan(roughness * 0.5 * M_PI * 0.99), max_distance, gi_probes.data[index].bias);
+	if (gi_probes.data[index].blend_ambient) {
+		irr_light.rgb = mix(environment, irr_light.rgb, min(1.0, irr_light.a / 0.95));
+	}
+	irr_light.rgb *= gi_probes.data[index].dynamic_range;
+	//irr_light=vec3(0.0);
+
+	out_spec += vec4(irr_light.rgb * blend, blend);
+}
+
+vec2 octahedron_wrap(vec2 v) {
+	vec2 signVal;
+	signVal.x = v.x >= 0.0 ? 1.0 : -1.0;
+	signVal.y = v.y >= 0.0 ? 1.0 : -1.0;
+	return (1.0 - abs(v.yx)) * signVal;
+}
+
+vec2 octahedron_encode(vec3 n) {
+	// https://twitter.com/Stubbesaurus/status/937994790553227264
+	n /= (abs(n.x) + abs(n.y) + abs(n.z));
+	n.xy = n.z >= 0.0 ? n.xy : octahedron_wrap(n.xy);
+	n.xy = n.xy * 0.5 + 0.5;
+	return n.xy;
+}
+
+void sdfgi_process(uint cascade, vec3 cascade_pos, vec3 cam_pos, vec3 cam_normal, vec3 cam_specular_normal, bool use_specular, float roughness, out vec3 diffuse_light, out vec3 specular_light, out float blend) {
+	cascade_pos += cam_normal * sdfgi.normal_bias;
+
+	vec3 base_pos = floor(cascade_pos);
+	//cascade_pos += mix(vec3(0.0),vec3(0.01),lessThan(abs(cascade_pos-base_pos),vec3(0.01))) * cam_normal;
+	ivec3 probe_base_pos = ivec3(base_pos);
+
+	vec4 diffuse_accum = vec4(0.0);
+	vec3 specular_accum;
+
+	ivec3 tex_pos = ivec3(probe_base_pos.xy, int(cascade));
+	tex_pos.x += probe_base_pos.z * sdfgi.probe_axis_size;
+	tex_pos.xy = tex_pos.xy * (SDFGI_OCT_SIZE + 2) + ivec2(1);
+
+	vec3 diffuse_posf = (vec3(tex_pos) + vec3(octahedron_encode(cam_normal) * float(SDFGI_OCT_SIZE), 0.0)) * sdfgi.lightprobe_tex_pixel_size;
+
+	vec3 specular_posf;
+
+	if (use_specular) {
+		specular_accum = vec3(0.0);
+		specular_posf = (vec3(tex_pos) + vec3(octahedron_encode(cam_specular_normal) * float(SDFGI_OCT_SIZE), 0.0)) * sdfgi.lightprobe_tex_pixel_size;
+	}
+
+	vec4 light_accum = vec4(0.0);
+	float weight_accum = 0.0;
+
+	for (uint j = 0; j < 8; j++) {
+		ivec3 offset = (ivec3(j) >> ivec3(0, 1, 2)) & ivec3(1, 1, 1);
+		ivec3 probe_posi = probe_base_pos;
+		probe_posi += offset;
+
+		// Compute weight
+
+		vec3 probe_pos = vec3(probe_posi);
+		vec3 probe_to_pos = cascade_pos - probe_pos;
+		vec3 probe_dir = normalize(-probe_to_pos);
+
+		vec3 trilinear = vec3(1.0) - abs(probe_to_pos);
+		float weight = trilinear.x * trilinear.y * trilinear.z * max(0.005, dot(cam_normal, probe_dir));
+
+		// Compute lightprobe occlusion
+
+		if (sdfgi.use_occlusion) {
+			ivec3 occ_indexv = abs((sdfgi.cascades[cascade].probe_world_offset + probe_posi) & ivec3(1, 1, 1)) * ivec3(1, 2, 4);
+			vec4 occ_mask = mix(vec4(0.0), vec4(1.0), equal(ivec4(occ_indexv.x | occ_indexv.y), ivec4(0, 1, 2, 3)));
+
+			vec3 occ_pos = clamp(cascade_pos, probe_pos - sdfgi.occlusion_clamp, probe_pos + sdfgi.occlusion_clamp) * sdfgi.probe_to_uvw;
+			occ_pos.z += float(cascade);
+			if (occ_indexv.z != 0) { //z bit is on, means index is >=4, so make it switch to the other half of textures
+				occ_pos.x += 1.0;
+			}
+
+			occ_pos *= sdfgi.occlusion_renormalize;
+			float occlusion = dot(textureLod(sampler3D(sdfgi_occlusion_cascades, material_samplers[SAMPLER_LINEAR_CLAMP]), occ_pos, 0.0), occ_mask);
+
+			weight *= max(occlusion, 0.01);
+		}
+
+		// Compute lightprobe texture position
+
+		vec3 diffuse;
+		vec3 pos_uvw = diffuse_posf;
+		pos_uvw.xy += vec2(offset.xy) * sdfgi.lightprobe_uv_offset.xy;
+		pos_uvw.x += float(offset.z) * sdfgi.lightprobe_uv_offset.z;
+		diffuse = textureLod(sampler2DArray(sdfgi_lightprobe_texture, material_samplers[SAMPLER_LINEAR_CLAMP]), pos_uvw, 0.0).rgb;
+
+		diffuse_accum += vec4(diffuse * weight, weight);
+
+		if (use_specular) {
+			vec3 specular = vec3(0.0);
+			vec3 pos_uvw = specular_posf;
+			pos_uvw.xy += vec2(offset.xy) * sdfgi.lightprobe_uv_offset.xy;
+			pos_uvw.x += float(offset.z) * sdfgi.lightprobe_uv_offset.z;
+			if (roughness < 0.99) {
+				specular = textureLod(sampler2DArray(sdfgi_lightprobe_texture, material_samplers[SAMPLER_LINEAR_CLAMP]), pos_uvw + vec3(0, 0, float(sdfgi.max_cascades)), 0.0).rgb;
+			}
+			if (roughness > 0.5) {
+				specular = mix(specular, textureLod(sampler2DArray(sdfgi_lightprobe_texture, material_samplers[SAMPLER_LINEAR_CLAMP]), pos_uvw, 0.0).rgb, (roughness - 0.5) * 2.0);
+			}
+
+			specular_accum += specular * weight;
+		}
+	}
+
+	if (diffuse_accum.a > 0.0) {
+		diffuse_accum.rgb /= diffuse_accum.a;
+	}
+
+	diffuse_light = diffuse_accum.rgb;
+
+	if (use_specular) {
+		if (diffuse_accum.a > 0.0) {
+			specular_accum /= diffuse_accum.a;
+		}
+
+		specular_light = specular_accum;
+	}
+
+	{
+		//process blend
+		float blend_from = (float(sdfgi.probe_axis_size - 1) / 2.0) - 2.5;
+		float blend_to = blend_from + 2.0;
+
+		vec3 inner_pos = cam_pos * sdfgi.cascades[cascade].to_probe;
+
+		float len = length(inner_pos);
+
+		inner_pos = abs(normalize(inner_pos));
+		len *= max(inner_pos.x, max(inner_pos.y, inner_pos.z));
+
+		if (len >= blend_from) {
+			blend = smoothstep(blend_from, blend_to, len);
+		} else {
+			blend = 0.0;
+		}
+	}
+}

+ 1023 - 0
servers/rendering/renderer_rd/shaders/scene_forward_lights_inc.glsl

@@ -0,0 +1,1023 @@
+// Functions related to lighting
+
+// This returns the G_GGX function divided by 2 cos_theta_m, where in practice cos_theta_m is either N.L or N.V.
+// We're dividing this factor off because the overall term we'll end up looks like
+// (see, for example, the first unnumbered equation in B. Burley, "Physically Based Shading at Disney", SIGGRAPH 2012):
+//
+//   F(L.V) D(N.H) G(N.L) G(N.V) / (4 N.L N.V)
+//
+// We're basically regouping this as
+//
+//   F(L.V) D(N.H) [G(N.L)/(2 N.L)] [G(N.V) / (2 N.V)]
+//
+// and thus, this function implements the [G(N.m)/(2 N.m)] part with m = L or V.
+//
+// The contents of the D and G (G1) functions (GGX) are taken from
+// E. Heitz, "Understanding the Masking-Shadowing Function in Microfacet-Based BRDFs", J. Comp. Graph. Tech. 3 (2) (2014).
+// Eqns 71-72 and 85-86 (see also Eqns 43 and 80).
+
+float G_GGX_2cos(float cos_theta_m, float alpha) {
+	// Schlick's approximation
+	// C. Schlick, "An Inexpensive BRDF Model for Physically-based Rendering", Computer Graphics Forum. 13 (3): 233 (1994)
+	// Eq. (19), although see Heitz (2014) the about the problems with his derivation.
+	// It nevertheless approximates GGX well with k = alpha/2.
+	float k = 0.5 * alpha;
+	return 0.5 / (cos_theta_m * (1.0 - k) + k);
+
+	// float cos2 = cos_theta_m * cos_theta_m;
+	// float sin2 = (1.0 - cos2);
+	// return 1.0 / (cos_theta_m + sqrt(cos2 + alpha * alpha * sin2));
+}
+
+float D_GGX(float cos_theta_m, float alpha) {
+	float alpha2 = alpha * alpha;
+	float d = 1.0 + (alpha2 - 1.0) * cos_theta_m * cos_theta_m;
+	return alpha2 / (M_PI * d * d);
+}
+
+float G_GGX_anisotropic_2cos(float cos_theta_m, float alpha_x, float alpha_y, float cos_phi, float sin_phi) {
+	float cos2 = cos_theta_m * cos_theta_m;
+	float sin2 = (1.0 - cos2);
+	float s_x = alpha_x * cos_phi;
+	float s_y = alpha_y * sin_phi;
+	return 1.0 / max(cos_theta_m + sqrt(cos2 + (s_x * s_x + s_y * s_y) * sin2), 0.001);
+}
+
+float D_GGX_anisotropic(float cos_theta_m, float alpha_x, float alpha_y, float cos_phi, float sin_phi) {
+	float cos2 = cos_theta_m * cos_theta_m;
+	float sin2 = (1.0 - cos2);
+	float r_x = cos_phi / alpha_x;
+	float r_y = sin_phi / alpha_y;
+	float d = cos2 + sin2 * (r_x * r_x + r_y * r_y);
+	return 1.0 / max(M_PI * alpha_x * alpha_y * d * d, 0.001);
+}
+
+float SchlickFresnel(float u) {
+	float m = 1.0 - u;
+	float m2 = m * m;
+	return m2 * m2 * m; // pow(m,5)
+}
+
+float GTR1(float NdotH, float a) {
+	if (a >= 1.0)
+		return 1.0 / M_PI;
+	float a2 = a * a;
+	float t = 1.0 + (a2 - 1.0) * NdotH * NdotH;
+	return (a2 - 1.0) / (M_PI * log(a2) * t);
+}
+
+vec3 F0(float metallic, float specular, vec3 albedo) {
+	float dielectric = 0.16 * specular * specular;
+	// use albedo * metallic as colored specular reflectance at 0 angle for metallic materials;
+	// see https://google.github.io/filament/Filament.md.html
+	return mix(vec3(dielectric), albedo, vec3(metallic));
+}
+
+void light_compute(vec3 N, vec3 L, vec3 V, vec3 light_color, float attenuation, vec3 f0, uint orms, float specular_amount,
+#ifdef LIGHT_BACKLIGHT_USED
+		vec3 backlight,
+#endif
+#ifdef LIGHT_TRANSMITTANCE_USED
+		vec4 transmittance_color,
+		float transmittance_depth,
+		float transmittance_curve,
+		float transmittance_boost,
+		float transmittance_z,
+#endif
+#ifdef LIGHT_RIM_USED
+		float rim, float rim_tint, vec3 rim_color,
+#endif
+#ifdef LIGHT_CLEARCOAT_USED
+		float clearcoat, float clearcoat_gloss,
+#endif
+#ifdef LIGHT_ANISOTROPY_USED
+		vec3 B, vec3 T, float anisotropy,
+#endif
+#ifdef USE_SOFT_SHADOWS
+		float A,
+#endif
+#ifdef USE_SHADOW_TO_OPACITY
+		inout float alpha,
+#endif
+		inout vec3 diffuse_light, inout vec3 specular_light) {
+
+#if defined(LIGHT_CODE_USED)
+	// light is written by the light shader
+
+	vec3 normal = N;
+	vec3 light = L;
+	vec3 view = V;
+
+#CODE : LIGHT
+
+#else
+
+#ifdef USE_SOFT_SHADOWS
+	float NdotL = min(A + dot(N, L), 1.0);
+#else
+	float NdotL = dot(N, L);
+#endif
+	float cNdotL = max(NdotL, 0.0); // clamped NdotL
+	float NdotV = dot(N, V);
+	float cNdotV = max(NdotV, 0.0);
+
+#if defined(DIFFUSE_BURLEY) || defined(SPECULAR_BLINN) || defined(SPECULAR_SCHLICK_GGX) || defined(LIGHT_CLEARCOAT_USED)
+	vec3 H = normalize(V + L);
+#endif
+
+#if defined(SPECULAR_BLINN) || defined(SPECULAR_SCHLICK_GGX) || defined(LIGHT_CLEARCOAT_USED)
+#ifdef USE_SOFT_SHADOWS
+	float cNdotH = clamp(A + dot(N, H), 0.0, 1.0);
+#else
+	float cNdotH = clamp(dot(N, H), 0.0, 1.0);
+#endif
+#endif
+
+#if defined(DIFFUSE_BURLEY) || defined(SPECULAR_SCHLICK_GGX) || defined(LIGHT_CLEARCOAT_USED)
+#ifdef USE_SOFT_SHADOWS
+	float cLdotH = clamp(A + dot(L, H), 0.0, 1.0);
+#else
+	float cLdotH = clamp(dot(L, H), 0.0, 1.0);
+#endif
+#endif
+
+	float metallic = unpackUnorm4x8(orms).z;
+	if (metallic < 1.0) {
+		float roughness = unpackUnorm4x8(orms).y;
+
+#if defined(DIFFUSE_OREN_NAYAR)
+		vec3 diffuse_brdf_NL;
+#else
+		float diffuse_brdf_NL; // BRDF times N.L for calculating diffuse radiance
+#endif
+
+#if defined(DIFFUSE_LAMBERT_WRAP)
+		// energy conserving lambert wrap shader
+		diffuse_brdf_NL = max(0.0, (NdotL + roughness) / ((1.0 + roughness) * (1.0 + roughness)));
+#elif defined(DIFFUSE_TOON)
+
+		diffuse_brdf_NL = smoothstep(-roughness, max(roughness, 0.01), NdotL);
+
+#elif defined(DIFFUSE_BURLEY)
+
+		{
+			float FD90_minus_1 = 2.0 * cLdotH * cLdotH * roughness - 0.5;
+			float FdV = 1.0 + FD90_minus_1 * SchlickFresnel(cNdotV);
+			float FdL = 1.0 + FD90_minus_1 * SchlickFresnel(cNdotL);
+			diffuse_brdf_NL = (1.0 / M_PI) * FdV * FdL * cNdotL;
+			/*
+			float energyBias = mix(roughness, 0.0, 0.5);
+			float energyFactor = mix(roughness, 1.0, 1.0 / 1.51);
+			float fd90 = energyBias + 2.0 * VoH * VoH * roughness;
+			float f0 = 1.0;
+			float lightScatter = f0 + (fd90 - f0) * pow(1.0 - cNdotL, 5.0);
+			float viewScatter = f0 + (fd90 - f0) * pow(1.0 - cNdotV, 5.0);
+
+			diffuse_brdf_NL = lightScatter * viewScatter * energyFactor;
+			*/
+		}
+#else
+		// lambert
+		diffuse_brdf_NL = cNdotL * (1.0 / M_PI);
+#endif
+
+		diffuse_light += light_color * diffuse_brdf_NL * attenuation;
+
+#if defined(LIGHT_BACKLIGHT_USED)
+		diffuse_light += light_color * (vec3(1.0 / M_PI) - diffuse_brdf_NL) * backlight * attenuation;
+#endif
+
+#if defined(LIGHT_RIM_USED)
+		float rim_light = pow(max(0.0, 1.0 - cNdotV), max(0.0, (1.0 - roughness) * 16.0));
+		diffuse_light += rim_light * rim * mix(vec3(1.0), rim_color, rim_tint) * light_color;
+#endif
+
+#ifdef LIGHT_TRANSMITTANCE_USED
+
+#ifdef SSS_MODE_SKIN
+
+		{
+			float scale = 8.25 / transmittance_depth;
+			float d = scale * abs(transmittance_z);
+			float dd = -d * d;
+			vec3 profile = vec3(0.233, 0.455, 0.649) * exp(dd / 0.0064) +
+						   vec3(0.1, 0.336, 0.344) * exp(dd / 0.0484) +
+						   vec3(0.118, 0.198, 0.0) * exp(dd / 0.187) +
+						   vec3(0.113, 0.007, 0.007) * exp(dd / 0.567) +
+						   vec3(0.358, 0.004, 0.0) * exp(dd / 1.99) +
+						   vec3(0.078, 0.0, 0.0) * exp(dd / 7.41);
+
+			diffuse_light += profile * transmittance_color.a * light_color * clamp(transmittance_boost - NdotL, 0.0, 1.0) * (1.0 / M_PI);
+		}
+#else
+
+		if (transmittance_depth > 0.0) {
+			float fade = clamp(abs(transmittance_z / transmittance_depth), 0.0, 1.0);
+
+			fade = pow(max(0.0, 1.0 - fade), transmittance_curve);
+			fade *= clamp(transmittance_boost - NdotL, 0.0, 1.0);
+
+			diffuse_light += transmittance_color.rgb * light_color * (1.0 / M_PI) * transmittance_color.a * fade;
+		}
+
+#endif //SSS_MODE_SKIN
+
+#endif //LIGHT_TRANSMITTANCE_USED
+	}
+
+	float roughness = unpackUnorm4x8(orms).y;
+	if (roughness > 0.0) { // FIXME: roughness == 0 should not disable specular light entirely
+
+		// D
+
+#if defined(SPECULAR_BLINN)
+
+		//normalized blinn
+		float shininess = exp2(15.0 * (1.0 - roughness) + 1.0) * 0.25;
+		float blinn = pow(cNdotH, shininess) * cNdotL;
+		blinn *= (shininess + 8.0) * (1.0 / (8.0 * M_PI));
+		float intensity = blinn;
+
+		specular_light += light_color * intensity * attenuation * specular_amount;
+
+#elif defined(SPECULAR_PHONG)
+
+		vec3 R = normalize(-reflect(L, N));
+		float cRdotV = clamp(A + dot(R, V), 0.0, 1.0);
+		float shininess = exp2(15.0 * (1.0 - roughness) + 1.0) * 0.25;
+		float phong = pow(cRdotV, shininess);
+		phong *= (shininess + 8.0) * (1.0 / (8.0 * M_PI));
+		float intensity = (phong) / max(4.0 * cNdotV * cNdotL, 0.75);
+
+		specular_light += light_color * intensity * attenuation * specular_amount;
+
+#elif defined(SPECULAR_TOON)
+
+		vec3 R = normalize(-reflect(L, N));
+		float RdotV = dot(R, V);
+		float mid = 1.0 - roughness;
+		mid *= mid;
+		float intensity = smoothstep(mid - roughness * 0.5, mid + roughness * 0.5, RdotV) * mid;
+		diffuse_light += light_color * intensity * attenuation * specular_amount; // write to diffuse_light, as in toon shading you generally want no reflection
+
+#elif defined(SPECULAR_DISABLED)
+		// none..
+
+#elif defined(SPECULAR_SCHLICK_GGX)
+		// shlick+ggx as default
+
+#if defined(LIGHT_ANISOTROPY_USED)
+
+		float alpha_ggx = roughness * roughness;
+		float aspect = sqrt(1.0 - anisotropy * 0.9);
+		float ax = alpha_ggx / aspect;
+		float ay = alpha_ggx * aspect;
+		float XdotH = dot(T, H);
+		float YdotH = dot(B, H);
+		float D = D_GGX_anisotropic(cNdotH, ax, ay, XdotH, YdotH);
+		float G = G_GGX_anisotropic_2cos(cNdotL, ax, ay, XdotH, YdotH) * G_GGX_anisotropic_2cos(cNdotV, ax, ay, XdotH, YdotH);
+
+#else
+		float alpha_ggx = roughness * roughness;
+		float D = D_GGX(cNdotH, alpha_ggx);
+		float G = G_GGX_2cos(cNdotL, alpha_ggx) * G_GGX_2cos(cNdotV, alpha_ggx);
+#endif
+		// F
+		float cLdotH5 = SchlickFresnel(cLdotH);
+		vec3 F = mix(vec3(cLdotH5), vec3(1.0), f0);
+
+		vec3 specular_brdf_NL = cNdotL * D * F * G;
+
+		specular_light += specular_brdf_NL * light_color * attenuation * specular_amount;
+#endif
+
+#if defined(LIGHT_CLEARCOAT_USED)
+
+#if !defined(SPECULAR_SCHLICK_GGX)
+		float cLdotH5 = SchlickFresnel(cLdotH);
+#endif
+		float Dr = GTR1(cNdotH, mix(.1, .001, clearcoat_gloss));
+		float Fr = mix(.04, 1.0, cLdotH5);
+		float Gr = G_GGX_2cos(cNdotL, .25) * G_GGX_2cos(cNdotV, .25);
+
+		float clearcoat_specular_brdf_NL = 0.25 * clearcoat * Gr * Fr * Dr * cNdotL;
+
+		specular_light += clearcoat_specular_brdf_NL * light_color * attenuation * specular_amount;
+#endif
+	}
+
+#ifdef USE_SHADOW_TO_OPACITY
+	alpha = min(alpha, clamp(1.0 - attenuation), 0.0, 1.0));
+#endif
+
+#endif //defined(LIGHT_CODE_USED)
+}
+
+#ifndef USE_NO_SHADOWS
+
+// Interleaved Gradient Noise
+// http://www.iryoku.com/next-generation-post-processing-in-call-of-duty-advanced-warfare
+float quick_hash(vec2 pos) {
+	const vec3 magic = vec3(0.06711056f, 0.00583715f, 52.9829189f);
+	return fract(magic.z * fract(dot(pos, magic.xy)));
+}
+
+float sample_directional_pcf_shadow(texture2D shadow, vec2 shadow_pixel_size, vec4 coord) {
+	vec2 pos = coord.xy;
+	float depth = coord.z;
+
+	//if only one sample is taken, take it from the center
+	if (scene_data.directional_soft_shadow_samples == 1) {
+		return textureProj(sampler2DShadow(shadow, shadow_sampler), vec4(pos, depth, 1.0));
+	}
+
+	mat2 disk_rotation;
+	{
+		float r = quick_hash(gl_FragCoord.xy) * 2.0 * M_PI;
+		float sr = sin(r);
+		float cr = cos(r);
+		disk_rotation = mat2(vec2(cr, -sr), vec2(sr, cr));
+	}
+
+	float avg = 0.0;
+
+	for (uint i = 0; i < scene_data.directional_soft_shadow_samples; i++) {
+		avg += textureProj(sampler2DShadow(shadow, shadow_sampler), vec4(pos + shadow_pixel_size * (disk_rotation * scene_data.directional_soft_shadow_kernel[i].xy), depth, 1.0));
+	}
+
+	return avg * (1.0 / float(scene_data.directional_soft_shadow_samples));
+}
+
+float sample_pcf_shadow(texture2D shadow, vec2 shadow_pixel_size, vec4 coord) {
+	vec2 pos = coord.xy;
+	float depth = coord.z;
+
+	//if only one sample is taken, take it from the center
+	if (scene_data.soft_shadow_samples == 1) {
+		return textureProj(sampler2DShadow(shadow, shadow_sampler), vec4(pos, depth, 1.0));
+	}
+
+	mat2 disk_rotation;
+	{
+		float r = quick_hash(gl_FragCoord.xy) * 2.0 * M_PI;
+		float sr = sin(r);
+		float cr = cos(r);
+		disk_rotation = mat2(vec2(cr, -sr), vec2(sr, cr));
+	}
+
+	float avg = 0.0;
+
+	for (uint i = 0; i < scene_data.soft_shadow_samples; i++) {
+		avg += textureProj(sampler2DShadow(shadow, shadow_sampler), vec4(pos + shadow_pixel_size * (disk_rotation * scene_data.soft_shadow_kernel[i].xy), depth, 1.0));
+	}
+
+	return avg * (1.0 / float(scene_data.soft_shadow_samples));
+}
+
+float sample_directional_soft_shadow(texture2D shadow, vec3 pssm_coord, vec2 tex_scale) {
+	//find blocker
+	float blocker_count = 0.0;
+	float blocker_average = 0.0;
+
+	mat2 disk_rotation;
+	{
+		float r = quick_hash(gl_FragCoord.xy) * 2.0 * M_PI;
+		float sr = sin(r);
+		float cr = cos(r);
+		disk_rotation = mat2(vec2(cr, -sr), vec2(sr, cr));
+	}
+
+	for (uint i = 0; i < scene_data.directional_penumbra_shadow_samples; i++) {
+		vec2 suv = pssm_coord.xy + (disk_rotation * scene_data.directional_penumbra_shadow_kernel[i].xy) * tex_scale;
+		float d = textureLod(sampler2D(shadow, material_samplers[SAMPLER_LINEAR_CLAMP]), suv, 0.0).r;
+		if (d < pssm_coord.z) {
+			blocker_average += d;
+			blocker_count += 1.0;
+		}
+	}
+
+	if (blocker_count > 0.0) {
+		//blockers found, do soft shadow
+		blocker_average /= blocker_count;
+		float penumbra = (pssm_coord.z - blocker_average) / blocker_average;
+		tex_scale *= penumbra;
+
+		float s = 0.0;
+		for (uint i = 0; i < scene_data.directional_penumbra_shadow_samples; i++) {
+			vec2 suv = pssm_coord.xy + (disk_rotation * scene_data.directional_penumbra_shadow_kernel[i].xy) * tex_scale;
+			s += textureProj(sampler2DShadow(shadow, shadow_sampler), vec4(suv, pssm_coord.z, 1.0));
+		}
+
+		return s / float(scene_data.directional_penumbra_shadow_samples);
+
+	} else {
+		//no blockers found, so no shadow
+		return 1.0;
+	}
+}
+
+#endif //USE_NO_SHADOWS
+
+float get_omni_attenuation(float distance, float inv_range, float decay) {
+	float nd = distance * inv_range;
+	nd *= nd;
+	nd *= nd; // nd^4
+	nd = max(1.0 - nd, 0.0);
+	nd *= nd; // nd^2
+	return nd * pow(max(distance, 0.0001), -decay);
+}
+
+float light_process_omni_shadow(uint idx, vec3 vertex, vec3 normal) {
+#ifndef USE_NO_SHADOWS
+	if (omni_lights.data[idx].shadow_enabled) {
+		// there is a shadowmap
+
+		vec3 light_rel_vec = omni_lights.data[idx].position - vertex;
+		float light_length = length(light_rel_vec);
+
+		vec4 v = vec4(vertex, 1.0);
+
+		vec4 splane = (omni_lights.data[idx].shadow_matrix * v);
+		float shadow_len = length(splane.xyz); //need to remember shadow len from here
+
+		{
+			vec3 nofs = normal_interp * omni_lights.data[idx].shadow_normal_bias / omni_lights.data[idx].inv_radius;
+			nofs *= (1.0 - max(0.0, dot(normalize(light_rel_vec), normalize(normal_interp))));
+			v.xyz += nofs;
+			splane = (omni_lights.data[idx].shadow_matrix * v);
+		}
+
+		float shadow;
+
+#ifdef USE_SOFT_SHADOWS
+		if (omni_lights.data[idx].soft_shadow_size > 0.0) {
+			//soft shadow
+
+			//find blocker
+
+			float blocker_count = 0.0;
+			float blocker_average = 0.0;
+
+			mat2 disk_rotation;
+			{
+				float r = quick_hash(gl_FragCoord.xy) * 2.0 * M_PI;
+				float sr = sin(r);
+				float cr = cos(r);
+				disk_rotation = mat2(vec2(cr, -sr), vec2(sr, cr));
+			}
+
+			vec3 normal = normalize(splane.xyz);
+			vec3 v0 = abs(normal.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(0.0, 1.0, 0.0);
+			vec3 tangent = normalize(cross(v0, normal));
+			vec3 bitangent = normalize(cross(tangent, normal));
+			float z_norm = shadow_len * omni_lights.data[idx].inv_radius;
+
+			tangent *= omni_lights.data[idx].soft_shadow_size * omni_lights.data[idx].soft_shadow_scale;
+			bitangent *= omni_lights.data[idx].soft_shadow_size * omni_lights.data[idx].soft_shadow_scale;
+
+			for (uint i = 0; i < scene_data.penumbra_shadow_samples; i++) {
+				vec2 disk = disk_rotation * scene_data.penumbra_shadow_kernel[i].xy;
+
+				vec3 pos = splane.xyz + tangent * disk.x + bitangent * disk.y;
+
+				pos = normalize(pos);
+				vec4 uv_rect = omni_lights.data[idx].atlas_rect;
+
+				if (pos.z >= 0.0) {
+					pos.z += 1.0;
+					uv_rect.y += uv_rect.w;
+				} else {
+					pos.z = 1.0 - pos.z;
+				}
+
+				pos.xy /= pos.z;
+
+				pos.xy = pos.xy * 0.5 + 0.5;
+				pos.xy = uv_rect.xy + pos.xy * uv_rect.zw;
+
+				float d = textureLod(sampler2D(shadow_atlas, material_samplers[SAMPLER_LINEAR_CLAMP]), pos.xy, 0.0).r;
+				if (d < z_norm) {
+					blocker_average += d;
+					blocker_count += 1.0;
+				}
+			}
+
+			if (blocker_count > 0.0) {
+				//blockers found, do soft shadow
+				blocker_average /= blocker_count;
+				float penumbra = (z_norm - blocker_average) / blocker_average;
+				tangent *= penumbra;
+				bitangent *= penumbra;
+
+				z_norm -= omni_lights.data[idx].inv_radius * omni_lights.data[idx].shadow_bias;
+
+				shadow = 0.0;
+				for (uint i = 0; i < scene_data.penumbra_shadow_samples; i++) {
+					vec2 disk = disk_rotation * scene_data.penumbra_shadow_kernel[i].xy;
+					vec3 pos = splane.xyz + tangent * disk.x + bitangent * disk.y;
+
+					pos = normalize(pos);
+					vec4 uv_rect = omni_lights.data[idx].atlas_rect;
+
+					if (pos.z >= 0.0) {
+						pos.z += 1.0;
+						uv_rect.y += uv_rect.w;
+					} else {
+						pos.z = 1.0 - pos.z;
+					}
+
+					pos.xy /= pos.z;
+
+					pos.xy = pos.xy * 0.5 + 0.5;
+					pos.xy = uv_rect.xy + pos.xy * uv_rect.zw;
+					shadow += textureProj(sampler2DShadow(shadow_atlas, shadow_sampler), vec4(pos.xy, z_norm, 1.0));
+				}
+
+				shadow /= float(scene_data.penumbra_shadow_samples);
+
+			} else {
+				//no blockers found, so no shadow
+				shadow = 1.0;
+			}
+		} else {
+#endif
+			splane.xyz = normalize(splane.xyz);
+			vec4 clamp_rect = omni_lights.data[idx].atlas_rect;
+
+			if (splane.z >= 0.0) {
+				splane.z += 1.0;
+
+				clamp_rect.y += clamp_rect.w;
+
+			} else {
+				splane.z = 1.0 - splane.z;
+			}
+
+			splane.xy /= splane.z;
+
+			splane.xy = splane.xy * 0.5 + 0.5;
+			splane.z = (shadow_len - omni_lights.data[idx].shadow_bias) * omni_lights.data[idx].inv_radius;
+			splane.xy = clamp_rect.xy + splane.xy * clamp_rect.zw;
+			splane.w = 1.0; //needed? i think it should be 1 already
+			shadow = sample_pcf_shadow(shadow_atlas, omni_lights.data[idx].soft_shadow_scale * scene_data.shadow_atlas_pixel_size, splane);
+#ifdef USE_SOFT_SHADOWS
+		}
+#endif
+
+		return shadow;
+	}
+#endif
+
+	return 1.0;
+}
+
+void light_process_omni(uint idx, vec3 vertex, vec3 eye_vec, vec3 normal, vec3 vertex_ddx, vec3 vertex_ddy, vec3 f0, uint orms, float shadow,
+#ifdef LIGHT_BACKLIGHT_USED
+		vec3 backlight,
+#endif
+#ifdef LIGHT_TRANSMITTANCE_USED
+		vec4 transmittance_color,
+		float transmittance_depth,
+		float transmittance_curve,
+		float transmittance_boost,
+#endif
+#ifdef LIGHT_RIM_USED
+		float rim, float rim_tint, vec3 rim_color,
+#endif
+#ifdef LIGHT_CLEARCOAT_USED
+		float clearcoat, float clearcoat_gloss,
+#endif
+#ifdef LIGHT_ANISOTROPY_USED
+		vec3 binormal, vec3 tangent, float anisotropy,
+#endif
+#ifdef USE_SHADOW_TO_OPACITY
+		inout float alpha,
+#endif
+		inout vec3 diffuse_light, inout vec3 specular_light) {
+	vec3 light_rel_vec = omni_lights.data[idx].position - vertex;
+	float light_length = length(light_rel_vec);
+	float omni_attenuation = get_omni_attenuation(light_length, omni_lights.data[idx].inv_radius, omni_lights.data[idx].attenuation);
+	float light_attenuation = omni_attenuation;
+	vec3 color = omni_lights.data[idx].color;
+
+#ifdef USE_SOFT_SHADOWS
+	float size_A = 0.0;
+
+	if (omni_lights.data[idx].size > 0.0) {
+		float t = omni_lights.data[idx].size / max(0.001, light_length);
+		size_A = max(0.0, 1.0 - 1 / sqrt(1 + t * t));
+	}
+#endif
+
+#ifdef LIGHT_TRANSMITTANCE_USED
+	float transmittance_z = transmittance_depth; //no transmittance by default
+	transmittance_color.a *= light_attenuation;
+	{
+		vec4 clamp_rect = omni_lights.data[idx].atlas_rect;
+
+		//redo shadowmapping, but shrink the model a bit to avoid arctifacts
+		vec4 splane = (omni_lights.data[idx].shadow_matrix * vec4(vertex - normalize(normal_interp) * omni_lights.data[idx].transmittance_bias, 1.0));
+
+		shadow_len = length(splane.xyz);
+		splane = normalize(splane.xyz);
+
+		if (splane.z >= 0.0) {
+			splane.z += 1.0;
+
+		} else {
+			splane.z = 1.0 - splane.z;
+		}
+
+		splane.xy /= splane.z;
+		splane.xy = splane.xy * 0.5 + 0.5;
+		splane.z = shadow_len * omni_lights.data[idx].inv_radius;
+		splane.xy = clamp_rect.xy + splane.xy * clamp_rect.zw;
+		splane.w = 1.0; //needed? i think it should be 1 already
+
+		float shadow_z = textureLod(sampler2D(shadow_atlas, material_samplers[SAMPLER_LINEAR_CLAMP]), splane.xy, 0.0).r;
+		transmittance_z = (splane.z - shadow_z) / omni_lights.data[idx].inv_radius;
+	}
+#endif
+
+#if 0
+
+	if (omni_lights.data[idx].projector_rect != vec4(0.0)) {
+		vec3 local_v = (omni_lights.data[idx].shadow_matrix * vec4(vertex, 1.0)).xyz;
+		local_v = normalize(local_v);
+
+		vec4 atlas_rect = omni_lights.data[idx].projector_rect;
+
+		if (local_v.z >= 0.0) {
+			local_v.z += 1.0;
+			atlas_rect.y += atlas_rect.w;
+
+		} else {
+			local_v.z = 1.0 - local_v.z;
+		}
+
+		local_v.xy /= local_v.z;
+		local_v.xy = local_v.xy * 0.5 + 0.5;
+		vec2 proj_uv = local_v.xy * atlas_rect.zw;
+
+		vec2 proj_uv_ddx;
+		vec2 proj_uv_ddy;
+		{
+			vec3 local_v_ddx = (omni_lights.data[idx].shadow_matrix * vec4(vertex + vertex_ddx, 1.0)).xyz;
+			local_v_ddx = normalize(local_v_ddx);
+
+			if (local_v_ddx.z >= 0.0) {
+				local_v_ddx.z += 1.0;
+			} else {
+				local_v_ddx.z = 1.0 - local_v_ddx.z;
+			}
+
+			local_v_ddx.xy /= local_v_ddx.z;
+			local_v_ddx.xy = local_v_ddx.xy * 0.5 + 0.5;
+
+			proj_uv_ddx = local_v_ddx.xy * atlas_rect.zw - proj_uv;
+
+			vec3 local_v_ddy = (omni_lights.data[idx].shadow_matrix * vec4(vertex + vertex_ddy, 1.0)).xyz;
+			local_v_ddy = normalize(local_v_ddy);
+
+			if (local_v_ddy.z >= 0.0) {
+				local_v_ddy.z += 1.0;
+			} else {
+				local_v_ddy.z = 1.0 - local_v_ddy.z;
+			}
+
+			local_v_ddy.xy /= local_v_ddy.z;
+			local_v_ddy.xy = local_v_ddy.xy * 0.5 + 0.5;
+
+			proj_uv_ddy = local_v_ddy.xy * atlas_rect.zw - proj_uv;
+		}
+
+		vec4 proj = textureGrad(sampler2D(decal_atlas_srgb, material_samplers[SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP]), proj_uv + atlas_rect.xy, proj_uv_ddx, proj_uv_ddy);
+		no_shadow = mix(no_shadow, proj.rgb, proj.a);
+	}
+#endif
+
+	light_attenuation *= shadow;
+
+	light_compute(normal, normalize(light_rel_vec), eye_vec, color, light_attenuation, f0, orms, omni_lights.data[idx].specular_amount,
+#ifdef LIGHT_BACKLIGHT_USED
+			backlight,
+#endif
+#ifdef LIGHT_TRANSMITTANCE_USED
+			transmittance_color,
+			transmittance_depth,
+			transmittance_curve,
+			transmittance_boost,
+			transmittance_z,
+#endif
+#ifdef LIGHT_RIM_USED
+			rim * omni_attenuation, rim_tint, rim_color,
+#endif
+#ifdef LIGHT_CLEARCOAT_USED
+			clearcoat, clearcoat_gloss,
+#endif
+#ifdef LIGHT_ANISOTROPY_USED
+			binormal, tangent, anisotropy,
+#endif
+#ifdef USE_SOFT_SHADOWS
+			size_A,
+#endif
+#ifdef USE_SHADOW_TO_OPACITY
+			alpha,
+#endif
+			diffuse_light,
+			specular_light);
+}
+
+float light_process_spot_shadow(uint idx, vec3 vertex, vec3 normal) {
+#ifndef USE_NO_SHADOWS
+	if (spot_lights.data[idx].shadow_enabled) {
+		vec3 light_rel_vec = spot_lights.data[idx].position - vertex;
+		float light_length = length(light_rel_vec);
+		vec3 spot_dir = spot_lights.data[idx].direction;
+		//there is a shadowmap
+		vec4 v = vec4(vertex, 1.0);
+
+		v.xyz -= spot_dir * spot_lights.data[idx].shadow_bias;
+
+		float z_norm = dot(spot_dir, -light_rel_vec) * spot_lights.data[idx].inv_radius;
+
+		float depth_bias_scale = 1.0 / (max(0.0001, z_norm)); //the closer to the light origin, the more you have to offset to reach 1px in the map
+		vec3 normal_bias = normalize(normal_interp) * (1.0 - max(0.0, dot(spot_dir, -normalize(normal_interp)))) * spot_lights.data[idx].shadow_normal_bias * depth_bias_scale;
+		normal_bias -= spot_dir * dot(spot_dir, normal_bias); //only XY, no Z
+		v.xyz += normal_bias;
+
+		//adjust with bias
+		z_norm = dot(spot_dir, v.xyz - spot_lights.data[idx].position) * spot_lights.data[idx].inv_radius;
+
+		float shadow;
+
+		vec4 splane = (spot_lights.data[idx].shadow_matrix * v);
+		splane /= splane.w;
+
+#ifdef USE_SOFT_SHADOWS
+		if (spot_lights.data[idx].soft_shadow_size > 0.0) {
+			//soft shadow
+
+			//find blocker
+
+			vec2 shadow_uv = splane.xy * spot_lights.data[idx].atlas_rect.zw + spot_lights.data[idx].atlas_rect.xy;
+
+			float blocker_count = 0.0;
+			float blocker_average = 0.0;
+
+			mat2 disk_rotation;
+			{
+				float r = quick_hash(gl_FragCoord.xy) * 2.0 * M_PI;
+				float sr = sin(r);
+				float cr = cos(r);
+				disk_rotation = mat2(vec2(cr, -sr), vec2(sr, cr));
+			}
+
+			float uv_size = spot_lights.data[idx].soft_shadow_size * z_norm * spot_lights.data[idx].soft_shadow_scale;
+			vec2 clamp_max = spot_lights.data[idx].atlas_rect.xy + spot_lights.data[idx].atlas_rect.zw;
+			for (uint i = 0; i < scene_data.penumbra_shadow_samples; i++) {
+				vec2 suv = shadow_uv + (disk_rotation * scene_data.penumbra_shadow_kernel[i].xy) * uv_size;
+				suv = clamp(suv, spot_lights.data[idx].atlas_rect.xy, clamp_max);
+				float d = textureLod(sampler2D(shadow_atlas, material_samplers[SAMPLER_LINEAR_CLAMP]), suv, 0.0).r;
+				if (d < z_norm) {
+					blocker_average += d;
+					blocker_count += 1.0;
+				}
+			}
+
+			if (blocker_count > 0.0) {
+				//blockers found, do soft shadow
+				blocker_average /= blocker_count;
+				float penumbra = (z_norm - blocker_average) / blocker_average;
+				uv_size *= penumbra;
+
+				shadow = 0.0;
+				for (uint i = 0; i < scene_data.penumbra_shadow_samples; i++) {
+					vec2 suv = shadow_uv + (disk_rotation * scene_data.penumbra_shadow_kernel[i].xy) * uv_size;
+					suv = clamp(suv, spot_lights.data[idx].atlas_rect.xy, clamp_max);
+					shadow += textureProj(sampler2DShadow(shadow_atlas, shadow_sampler), vec4(suv, z_norm, 1.0));
+				}
+
+				shadow /= float(scene_data.penumbra_shadow_samples);
+
+			} else {
+				//no blockers found, so no shadow
+				shadow = 1.0;
+			}
+
+		} else {
+#endif
+			//hard shadow
+			vec4 shadow_uv = vec4(splane.xy * spot_lights.data[idx].atlas_rect.zw + spot_lights.data[idx].atlas_rect.xy, splane.z, 1.0);
+
+			shadow = sample_pcf_shadow(shadow_atlas, spot_lights.data[idx].soft_shadow_scale * scene_data.shadow_atlas_pixel_size, shadow_uv);
+#ifdef USE_SOFT_SHADOWS
+		}
+#endif
+
+		return shadow;
+	}
+
+#endif //USE_NO_SHADOWS
+
+	return 1.0;
+}
+
+void light_process_spot(uint idx, vec3 vertex, vec3 eye_vec, vec3 normal, vec3 vertex_ddx, vec3 vertex_ddy, vec3 f0, uint orms, float shadow,
+#ifdef LIGHT_BACKLIGHT_USED
+		vec3 backlight,
+#endif
+#ifdef LIGHT_TRANSMITTANCE_USED
+		vec4 transmittance_color,
+		float transmittance_depth,
+		float transmittance_curve,
+		float transmittance_boost,
+#endif
+#ifdef LIGHT_RIM_USED
+		float rim, float rim_tint, vec3 rim_color,
+#endif
+#ifdef LIGHT_CLEARCOAT_USED
+		float clearcoat, float clearcoat_gloss,
+#endif
+#ifdef LIGHT_ANISOTROPY_USED
+		vec3 binormal, vec3 tangent, float anisotropy,
+#endif
+#ifdef USE_SHADOW_TO_OPACITY
+		inout float alpha,
+#endif
+		inout vec3 diffuse_light,
+		inout vec3 specular_light) {
+	vec3 light_rel_vec = spot_lights.data[idx].position - vertex;
+	float light_length = length(light_rel_vec);
+	float spot_attenuation = get_omni_attenuation(light_length, spot_lights.data[idx].inv_radius, spot_lights.data[idx].attenuation);
+	vec3 spot_dir = spot_lights.data[idx].direction;
+	float scos = max(dot(-normalize(light_rel_vec), spot_dir), spot_lights.data[idx].cone_angle);
+	float spot_rim = max(0.0001, (1.0 - scos) / (1.0 - spot_lights.data[idx].cone_angle));
+	spot_attenuation *= 1.0 - pow(spot_rim, spot_lights.data[idx].cone_attenuation);
+	float light_attenuation = spot_attenuation;
+	vec3 color = spot_lights.data[idx].color;
+	float specular_amount = spot_lights.data[idx].specular_amount;
+
+#ifdef USE_SOFT_SHADOWS
+	float size_A = 0.0;
+
+	if (spot_lights.data[idx].size > 0.0) {
+		float t = spot_lights.data[idx].size / max(0.001, light_length);
+		size_A = max(0.0, 1.0 - 1 / sqrt(1 + t * t));
+	}
+#endif
+
+	/*
+	if (spot_lights.data[idx].atlas_rect!=vec4(0.0)) {
+		//use projector texture
+	}
+	*/
+
+#ifdef LIGHT_TRANSMITTANCE_USED
+	float transmittance_z = transmittance_depth;
+	transmittance_color.a *= light_attenuation;
+	{
+		splane = (spot_lights.data[idx].shadow_matrix * vec4(vertex - normalize(normal_interp) * spot_lights.data[idx].transmittance_bias, 1.0));
+		splane /= splane.w;
+		splane.xy = splane.xy * spot_lights.data[idx].atlas_rect.zw + spot_lights.data[idx].atlas_rect.xy;
+
+		float shadow_z = textureLod(sampler2D(shadow_atlas, material_samplers[SAMPLER_LINEAR_CLAMP]), splane.xy, 0.0).r;
+		//reconstruct depth
+		shadow_z /= spot_lights.data[idx].inv_radius;
+		//distance to light plane
+		float z = dot(spot_dir, -light_rel_vec);
+		transmittance_z = z - shadow_z;
+	}
+#endif //LIGHT_TRANSMITTANCE_USED
+
+	light_attenuation *= shadow;
+
+	light_compute(normal, normalize(light_rel_vec), eye_vec, color, light_attenuation, f0, orms, spot_lights.data[idx].specular_amount,
+#ifdef LIGHT_BACKLIGHT_USED
+			backlight,
+#endif
+#ifdef LIGHT_TRANSMITTANCE_USED
+			transmittance_color,
+			transmittance_depth,
+			transmittance_curve,
+			transmittance_boost,
+			transmittance_z,
+#endif
+#ifdef LIGHT_RIM_USED
+			rim * spot_attenuation, rim_tint, rim_color,
+#endif
+#ifdef LIGHT_CLEARCOAT_USED
+			clearcoat, clearcoat_gloss,
+#endif
+#ifdef LIGHT_ANISOTROPY_USED
+			binormal, tangent, anisotropy,
+#endif
+#ifdef USE_SOFT_SHADOW
+			size_A,
+#endif
+#ifdef USE_SHADOW_TO_OPACITY
+			alpha,
+#endif
+			diffuse_light, specular_light);
+}
+
+void reflection_process(uint ref_index, vec3 vertex, vec3 normal, float roughness, vec3 ambient_light, vec3 specular_light, inout vec4 ambient_accum, inout vec4 reflection_accum) {
+	vec3 box_extents = reflections.data[ref_index].box_extents;
+	vec3 local_pos = (reflections.data[ref_index].local_matrix * vec4(vertex, 1.0)).xyz;
+
+	if (any(greaterThan(abs(local_pos), box_extents))) { //out of the reflection box
+		return;
+	}
+
+	vec3 ref_vec = normalize(reflect(vertex, normal));
+
+	vec3 inner_pos = abs(local_pos / box_extents);
+	float blend = max(inner_pos.x, max(inner_pos.y, inner_pos.z));
+	//make blend more rounded
+	blend = mix(length(inner_pos), blend, blend);
+	blend *= blend;
+	blend = max(0.0, 1.0 - blend);
+
+	if (reflections.data[ref_index].intensity > 0.0) { // compute reflection
+
+		vec3 local_ref_vec = (reflections.data[ref_index].local_matrix * vec4(ref_vec, 0.0)).xyz;
+
+		if (reflections.data[ref_index].box_project) { //box project
+
+			vec3 nrdir = normalize(local_ref_vec);
+			vec3 rbmax = (box_extents - local_pos) / nrdir;
+			vec3 rbmin = (-box_extents - local_pos) / nrdir;
+
+			vec3 rbminmax = mix(rbmin, rbmax, greaterThan(nrdir, vec3(0.0, 0.0, 0.0)));
+
+			float fa = min(min(rbminmax.x, rbminmax.y), rbminmax.z);
+			vec3 posonbox = local_pos + nrdir * fa;
+			local_ref_vec = posonbox - reflections.data[ref_index].box_offset;
+		}
+
+		vec4 reflection;
+
+		reflection.rgb = textureLod(samplerCubeArray(reflection_atlas, material_samplers[SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP]), vec4(local_ref_vec, reflections.data[ref_index].index), roughness * MAX_ROUGHNESS_LOD).rgb;
+
+		if (reflections.data[ref_index].exterior) {
+			reflection.rgb = mix(specular_light, reflection.rgb, blend);
+		}
+
+		reflection.rgb *= reflections.data[ref_index].intensity; //intensity
+		reflection.a = blend;
+		reflection.rgb *= reflection.a;
+
+		reflection_accum += reflection;
+	}
+
+	switch (reflections.data[ref_index].ambient_mode) {
+		case REFLECTION_AMBIENT_DISABLED: {
+			//do nothing
+		} break;
+		case REFLECTION_AMBIENT_ENVIRONMENT: {
+			//do nothing
+			vec3 local_amb_vec = (reflections.data[ref_index].local_matrix * vec4(normal, 0.0)).xyz;
+
+			vec4 ambient_out;
+
+			ambient_out.rgb = textureLod(samplerCubeArray(reflection_atlas, material_samplers[SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP]), vec4(local_amb_vec, reflections.data[ref_index].index), MAX_ROUGHNESS_LOD).rgb;
+			ambient_out.a = blend;
+			if (reflections.data[ref_index].exterior) {
+				ambient_out.rgb = mix(ambient_light, ambient_out.rgb, blend);
+			}
+
+			ambient_out.rgb *= ambient_out.a;
+			ambient_accum += ambient_out;
+		} break;
+		case REFLECTION_AMBIENT_COLOR: {
+			vec4 ambient_out;
+			ambient_out.a = blend;
+			ambient_out.rgb = reflections.data[ref_index].ambient;
+			if (reflections.data[ref_index].exterior) {
+				ambient_out.rgb = mix(ambient_light, ambient_out.rgb, blend);
+			}
+			ambient_out.rgb *= ambient_out.a;
+			ambient_accum += ambient_out;
+		} break;
+	}
+}
+
+float blur_shadow(float shadow) {
+	return shadow;
+#if 0
+	//disabling for now, will investigate later
+	float interp_shadow = shadow;
+	if (gl_HelperInvocation) {
+		interp_shadow = -4.0; // technically anything below -4 will do but just to make sure
+	}
+
+	uvec2 fc2 = uvec2(gl_FragCoord.xy);
+	interp_shadow -= dFdx(interp_shadow) * (float(fc2.x & 1) - 0.5);
+	interp_shadow -= dFdy(interp_shadow) * (float(fc2.y & 1) - 0.5);
+
+	if (interp_shadow >= 0.0) {
+		shadow = interp_shadow;
+	}
+	return shadow;
+#endif
+}

+ 1476 - 0
servers/rendering/renderer_rd/shaders/scene_forward_mobile.glsl

@@ -0,0 +1,1476 @@
+#[vertex]
+
+#version 450
+
+#VERSION_DEFINES
+
+/* Include our forward mobile UBOs definitions etc. */
+#include "scene_forward_mobile_inc.glsl"
+
+/* INPUT ATTRIBS */
+
+layout(location = 0) in vec3 vertex_attrib;
+
+//only for pure render depth when normal is not used
+
+#ifdef NORMAL_USED
+layout(location = 1) in vec3 normal_attrib;
+#endif
+
+#if defined(TANGENT_USED) || defined(NORMAL_MAP_USED) || defined(LIGHT_ANISOTROPY_USED)
+layout(location = 2) in vec4 tangent_attrib;
+#endif
+
+#if defined(COLOR_USED)
+layout(location = 3) in vec4 color_attrib;
+#endif
+
+#ifdef UV_USED
+layout(location = 4) in vec2 uv_attrib;
+#endif
+
+#if defined(UV2_USED) || defined(USE_LIGHTMAP) || defined(MODE_RENDER_MATERIAL)
+layout(location = 5) in vec2 uv2_attrib;
+#endif // MODE_RENDER_MATERIAL
+
+#if defined(CUSTOM0_USED)
+layout(location = 6) in vec4 custom0_attrib;
+#endif
+
+#if defined(CUSTOM1_USED)
+layout(location = 7) in vec4 custom1_attrib;
+#endif
+
+#if defined(CUSTOM2_USED)
+layout(location = 8) in vec4 custom2_attrib;
+#endif
+
+#if defined(CUSTOM3_USED)
+layout(location = 9) in vec4 custom3_attrib;
+#endif
+
+#if defined(BONES_USED) || defined(USE_PARTICLE_TRAILS)
+layout(location = 10) in uvec4 bone_attrib;
+#endif
+
+#if defined(WEIGHTS_USED) || defined(USE_PARTICLE_TRAILS)
+layout(location = 11) in vec4 weight_attrib;
+#endif
+
+/* Varyings */
+
+layout(location = 0) out vec3 vertex_interp;
+
+#ifdef NORMAL_USED
+layout(location = 1) out vec3 normal_interp;
+#endif
+
+#if defined(COLOR_USED)
+layout(location = 2) out vec4 color_interp;
+#endif
+
+#ifdef UV_USED
+layout(location = 3) out vec2 uv_interp;
+#endif
+
+#if defined(UV2_USED) || defined(USE_LIGHTMAP)
+layout(location = 4) out vec2 uv2_interp;
+#endif
+
+#if defined(TANGENT_USED) || defined(NORMAL_MAP_USED) || defined(LIGHT_ANISOTROPY_USED)
+layout(location = 5) out vec3 tangent_interp;
+layout(location = 6) out vec3 binormal_interp;
+#endif
+
+#ifdef MATERIAL_UNIFORMS_USED
+layout(set = MATERIAL_UNIFORM_SET, binding = 0, std140) uniform MaterialUniforms{
+
+#MATERIAL_UNIFORMS
+
+} material;
+#endif
+
+#ifdef MODE_DUAL_PARABOLOID
+
+layout(location = 8) out float dp_clip;
+
+#endif
+
+invariant gl_Position;
+
+#GLOBALS
+
+void main() {
+	vec4 instance_custom = vec4(0.0);
+#if defined(COLOR_USED)
+	color_interp = color_attrib;
+#endif
+
+	bool is_multimesh = bool(draw_call.flags & INSTANCE_FLAGS_MULTIMESH);
+
+	mat4 world_matrix = draw_call.transform;
+
+	mat3 world_normal_matrix;
+	if (bool(draw_call.flags & INSTANCE_FLAGS_NON_UNIFORM_SCALE)) {
+		world_normal_matrix = inverse(mat3(world_matrix));
+	} else {
+		world_normal_matrix = mat3(world_matrix);
+	}
+
+	if (is_multimesh) {
+		//multimesh, instances are for it
+
+		mat4 matrix;
+
+#ifdef USE_PARTICLE_TRAILS
+		uint trail_size = (draw_call.flags >> INSTANCE_FLAGS_PARTICLE_TRAIL_SHIFT) & INSTANCE_FLAGS_PARTICLE_TRAIL_MASK;
+		uint stride = 3 + 1 + 1; //particles always uses this format
+
+		uint offset = trail_size * stride * gl_InstanceIndex;
+
+#ifdef COLOR_USED
+		vec4 pcolor;
+#endif
+		{
+			uint boffset = offset + bone_attrib.x * stride;
+			matrix = mat4(transforms.data[boffset + 0], transforms.data[boffset + 1], transforms.data[boffset + 2], vec4(0.0, 0.0, 0.0, 1.0)) * weight_attrib.x;
+#ifdef COLOR_USED
+			pcolor = transforms.data[boffset + 3] * weight_attrib.x;
+#endif
+		}
+		if (weight_attrib.y > 0.001) {
+			uint boffset = offset + bone_attrib.y * stride;
+			matrix += mat4(transforms.data[boffset + 0], transforms.data[boffset + 1], transforms.data[boffset + 2], vec4(0.0, 0.0, 0.0, 1.0)) * weight_attrib.y;
+#ifdef COLOR_USED
+			pcolor += transforms.data[boffset + 3] * weight_attrib.y;
+#endif
+		}
+		if (weight_attrib.z > 0.001) {
+			uint boffset = offset + bone_attrib.z * stride;
+			matrix += mat4(transforms.data[boffset + 0], transforms.data[boffset + 1], transforms.data[boffset + 2], vec4(0.0, 0.0, 0.0, 1.0)) * weight_attrib.z;
+#ifdef COLOR_USED
+			pcolor += transforms.data[boffset + 3] * weight_attrib.z;
+#endif
+		}
+		if (weight_attrib.w > 0.001) {
+			uint boffset = offset + bone_attrib.w * stride;
+			matrix += mat4(transforms.data[boffset + 0], transforms.data[boffset + 1], transforms.data[boffset + 2], vec4(0.0, 0.0, 0.0, 1.0)) * weight_attrib.w;
+#ifdef COLOR_USED
+			pcolor += transforms.data[boffset + 3] * weight_attrib.w;
+#endif
+		}
+
+		instance_custom = transforms.data[offset + 4];
+
+#ifdef COLOR_USED
+		color_interp *= pcolor;
+#endif
+
+#else
+		uint stride = 0;
+		{
+			//TODO implement a small lookup table for the stride
+			if (bool(draw_call.flags & INSTANCE_FLAGS_MULTIMESH_FORMAT_2D)) {
+				stride += 2;
+			} else {
+				stride += 3;
+			}
+			if (bool(draw_call.flags & INSTANCE_FLAGS_MULTIMESH_HAS_COLOR)) {
+				stride += 1;
+			}
+			if (bool(draw_call.flags & INSTANCE_FLAGS_MULTIMESH_HAS_CUSTOM_DATA)) {
+				stride += 1;
+			}
+		}
+
+		uint offset = stride * gl_InstanceIndex;
+
+		if (bool(draw_call.flags & INSTANCE_FLAGS_MULTIMESH_FORMAT_2D)) {
+			matrix = mat4(transforms.data[offset + 0], transforms.data[offset + 1], vec4(0.0, 0.0, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 1.0));
+			offset += 2;
+		} else {
+			matrix = mat4(transforms.data[offset + 0], transforms.data[offset + 1], transforms.data[offset + 2], vec4(0.0, 0.0, 0.0, 1.0));
+			offset += 3;
+		}
+
+		if (bool(draw_call.flags & INSTANCE_FLAGS_MULTIMESH_HAS_COLOR)) {
+#ifdef COLOR_USED
+			color_interp *= transforms.data[offset];
+#endif
+			offset += 1;
+		}
+
+		if (bool(draw_call.flags & INSTANCE_FLAGS_MULTIMESH_HAS_CUSTOM_DATA)) {
+			instance_custom = transforms.data[offset];
+		}
+
+#endif
+		//transpose
+		matrix = transpose(matrix);
+		world_matrix = world_matrix * matrix;
+		world_normal_matrix = world_normal_matrix * mat3(matrix);
+	}
+
+	vec3 vertex = vertex_attrib;
+#ifdef NORMAL_USED
+	vec3 normal = normal_attrib * 2.0 - 1.0;
+#endif
+
+#if defined(TANGENT_USED) || defined(NORMAL_MAP_USED) || defined(LIGHT_ANISOTROPY_USED)
+	vec3 tangent = tangent_attrib.xyz * 2.0 - 1.0;
+	float binormalf = tangent_attrib.a * 2.0 - 1.0;
+	vec3 binormal = normalize(cross(normal, tangent) * binormalf);
+#endif
+
+#ifdef UV_USED
+	uv_interp = uv_attrib;
+#endif
+
+#if defined(UV2_USED) || defined(USE_LIGHTMAP)
+	uv2_interp = uv2_attrib;
+#endif
+
+#ifdef OVERRIDE_POSITION
+	vec4 position;
+#endif
+
+	mat4 projection_matrix = scene_data.projection_matrix;
+
+//using world coordinates
+#if !defined(SKIP_TRANSFORM_USED) && defined(VERTEX_WORLD_COORDS_USED)
+
+	vertex = (world_matrix * vec4(vertex, 1.0)).xyz;
+
+	normal = world_normal_matrix * normal;
+
+#if defined(TANGENT_USED) || defined(NORMAL_MAP_USED) || defined(LIGHT_ANISOTROPY_USED)
+
+	tangent = world_normal_matrix * tangent;
+	binormal = world_normal_matrix * binormal;
+
+#endif
+#endif
+
+	float roughness = 1.0;
+
+	mat4 modelview = scene_data.inv_camera_matrix * world_matrix;
+	mat3 modelview_normal = mat3(scene_data.inv_camera_matrix) * world_normal_matrix;
+
+	{
+#CODE : VERTEX
+	}
+
+	/* output */
+
+// using local coordinates (default)
+#if !defined(SKIP_TRANSFORM_USED) && !defined(VERTEX_WORLD_COORDS_USED)
+
+	vertex = (modelview * vec4(vertex, 1.0)).xyz;
+#ifdef NORMAL_USED
+	normal = modelview_normal * normal;
+#endif
+
+#endif
+
+#if defined(TANGENT_USED) || defined(NORMAL_MAP_USED) || defined(LIGHT_ANISOTROPY_USED)
+
+	binormal = modelview_normal * binormal;
+	tangent = modelview_normal * tangent;
+#endif
+
+//using world coordinates
+#if !defined(SKIP_TRANSFORM_USED) && defined(VERTEX_WORLD_COORDS_USED)
+
+	vertex = (scene_data.inv_camera_matrix * vec4(vertex, 1.0)).xyz;
+	normal = mat3(scene_data.inverse_normal_matrix) * normal;
+
+#if defined(TANGENT_USED) || defined(NORMAL_MAP_USED) || defined(LIGHT_ANISOTROPY_USED)
+
+	binormal = mat3(scene_data.camera_inverse_binormal_matrix) * binormal;
+	tangent = mat3(scene_data.camera_inverse_tangent_matrix) * tangent;
+#endif
+#endif
+
+	vertex_interp = vertex;
+#ifdef NORMAL_USED
+	normal_interp = normal;
+#endif
+
+#if defined(TANGENT_USED) || defined(NORMAL_MAP_USED) || defined(LIGHT_ANISOTROPY_USED)
+	tangent_interp = tangent;
+	binormal_interp = binormal;
+#endif
+
+#ifdef MODE_RENDER_DEPTH
+
+#ifdef MODE_DUAL_PARABOLOID
+
+	vertex_interp.z *= scene_data.dual_paraboloid_side;
+
+	dp_clip = vertex_interp.z; //this attempts to avoid noise caused by objects sent to the other parabolloid side due to bias
+
+	//for dual paraboloid shadow mapping, this is the fastest but least correct way, as it curves straight edges
+
+	vec3 vtx = vertex_interp;
+	float distance = length(vtx);
+	vtx = normalize(vtx);
+	vtx.xy /= 1.0 - vtx.z;
+	vtx.z = (distance / scene_data.z_far);
+	vtx.z = vtx.z * 2.0 - 1.0;
+	vertex_interp = vtx;
+
+#endif
+
+#endif //MODE_RENDER_DEPTH
+
+#ifdef OVERRIDE_POSITION
+	gl_Position = position;
+#else
+	gl_Position = projection_matrix * vec4(vertex_interp, 1.0);
+#endif // OVERRIDE_POSITION
+
+#ifdef MODE_RENDER_DEPTH
+	if (scene_data.pancake_shadows) {
+		if (gl_Position.z <= 0.00001) {
+			gl_Position.z = 0.00001;
+		}
+	}
+#endif // MODE_RENDER_DEPTH
+#ifdef MODE_RENDER_MATERIAL
+	if (scene_data.material_uv2_mode) {
+		vec2 uv_offset = draw_call.lightmap_uv_scale.xy; // we are abusing lightmap_uv_scale here, we shouldn't have a lightmap during a depth pass...
+		gl_Position.xy = (uv2_attrib.xy + uv_offset) * 2.0 - 1.0;
+		gl_Position.z = 0.00001;
+		gl_Position.w = 1.0;
+	}
+#endif // MODE_RENDER_MATERIAL
+}
+
+#[fragment]
+
+#version 450
+
+#VERSION_DEFINES
+
+/* Include our forward mobile UBOs definitions etc. */
+#include "scene_forward_mobile_inc.glsl"
+
+/* Varyings */
+
+layout(location = 0) in vec3 vertex_interp;
+
+#ifdef NORMAL_USED
+layout(location = 1) in vec3 normal_interp;
+#endif
+
+#if defined(COLOR_USED)
+layout(location = 2) in vec4 color_interp;
+#endif
+
+#ifdef UV_USED
+layout(location = 3) in vec2 uv_interp;
+#endif
+
+#if defined(UV2_USED) || defined(USE_LIGHTMAP)
+layout(location = 4) in vec2 uv2_interp;
+#endif
+
+#if defined(TANGENT_USED) || defined(NORMAL_MAP_USED) || defined(LIGHT_ANISOTROPY_USED)
+layout(location = 5) in vec3 tangent_interp;
+layout(location = 6) in vec3 binormal_interp;
+#endif
+
+#ifdef MODE_DUAL_PARABOLOID
+
+layout(location = 8) in float dp_clip;
+
+#endif
+
+//defines to keep compatibility with vertex
+
+#define world_matrix draw_call.transform
+#define projection_matrix scene_data.projection_matrix
+
+#if defined(ENABLE_SSS) && defined(ENABLE_TRANSMITTANCE)
+//both required for transmittance to be enabled
+#define LIGHT_TRANSMITTANCE_USED
+#endif
+
+#ifdef MATERIAL_UNIFORMS_USED
+layout(set = MATERIAL_UNIFORM_SET, binding = 0, std140) uniform MaterialUniforms{
+
+#MATERIAL_UNIFORMS
+
+} material;
+#endif
+
+#GLOBALS
+
+/* clang-format on */
+
+#ifdef MODE_RENDER_DEPTH
+
+#ifdef MODE_RENDER_MATERIAL
+
+layout(location = 0) out vec4 albedo_output_buffer;
+layout(location = 1) out vec4 normal_output_buffer;
+layout(location = 2) out vec4 orm_output_buffer;
+layout(location = 3) out vec4 emission_output_buffer;
+layout(location = 4) out float depth_output_buffer;
+
+#endif // MODE_RENDER_MATERIAL
+
+#else // RENDER DEPTH
+
+#ifdef MODE_MULTIPLE_RENDER_TARGETS
+
+layout(location = 0) out vec4 diffuse_buffer; //diffuse (rgb) and roughness
+layout(location = 1) out vec4 specular_buffer; //specular and SSS (subsurface scatter)
+#else
+
+layout(location = 0) out vec4 frag_color;
+#endif // MODE_MULTIPLE_RENDER_TARGETS
+
+#endif // RENDER DEPTH
+
+#include "scene_forward_aa_inc.glsl"
+
+#if !defined(MODE_RENDER_DEPTH) && !defined(MODE_UNSHADED)
+
+#include "scene_forward_lights_inc.glsl"
+
+#endif //!defined(MODE_RENDER_DEPTH) && !defined(MODE_UNSHADED)
+
+#ifndef MODE_RENDER_DEPTH
+
+/*
+	Only supporting normal fog here.
+*/
+
+vec4 fog_process(vec3 vertex) {
+	vec3 fog_color = scene_data.fog_light_color;
+
+	if (scene_data.fog_aerial_perspective > 0.0) {
+		vec3 sky_fog_color = vec3(0.0);
+		vec3 cube_view = scene_data.radiance_inverse_xform * vertex;
+		// mip_level always reads from the second mipmap and higher so the fog is always slightly blurred
+		float mip_level = mix(1.0 / MAX_ROUGHNESS_LOD, 1.0, 1.0 - (abs(vertex.z) - scene_data.z_near) / (scene_data.z_far - scene_data.z_near));
+#ifdef USE_RADIANCE_CUBEMAP_ARRAY
+		float lod, blend;
+		blend = modf(mip_level * MAX_ROUGHNESS_LOD, lod);
+		sky_fog_color = texture(samplerCubeArray(radiance_cubemap, material_samplers[SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP]), vec4(cube_view, lod)).rgb;
+		sky_fog_color = mix(sky_fog_color, texture(samplerCubeArray(radiance_cubemap, material_samplers[SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP]), vec4(cube_view, lod + 1)).rgb, blend);
+#else
+		sky_fog_color = textureLod(samplerCube(radiance_cubemap, material_samplers[SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP]), cube_view, mip_level * MAX_ROUGHNESS_LOD).rgb;
+#endif //USE_RADIANCE_CUBEMAP_ARRAY
+		fog_color = mix(fog_color, sky_fog_color, scene_data.fog_aerial_perspective);
+	}
+
+	if (scene_data.fog_sun_scatter > 0.001) {
+		vec4 sun_scatter = vec4(0.0);
+		float sun_total = 0.0;
+		vec3 view = normalize(vertex);
+
+		for (uint i = 0; i < scene_data.directional_light_count; i++) {
+			vec3 light_color = directional_lights.data[i].color * directional_lights.data[i].energy;
+			float light_amount = pow(max(dot(view, directional_lights.data[i].direction), 0.0), 8.0);
+			fog_color += light_color * light_amount * scene_data.fog_sun_scatter;
+		}
+	}
+
+	float fog_amount = 1.0 - exp(min(0.0, vertex.z * scene_data.fog_density));
+
+	if (abs(scene_data.fog_height_density) > 0.001) {
+		float y = (scene_data.camera_matrix * vec4(vertex, 1.0)).y;
+
+		float y_dist = scene_data.fog_height - y;
+
+		float vfog_amount = clamp(exp(y_dist * scene_data.fog_height_density), 0.0, 1.0);
+
+		fog_amount = max(vfog_amount, fog_amount);
+	}
+
+	return vec4(fog_color, fog_amount);
+}
+
+#endif //!MODE_RENDER DEPTH
+
+void main() {
+#ifdef MODE_DUAL_PARABOLOID
+
+	if (dp_clip > 0.0)
+		discard;
+#endif
+
+	//lay out everything, whathever is unused is optimized away anyway
+	vec3 vertex = vertex_interp;
+	vec3 view = -normalize(vertex_interp);
+	vec3 albedo = vec3(1.0);
+	vec3 backlight = vec3(0.0);
+	vec4 transmittance_color = vec4(0.0);
+	float transmittance_depth = 0.0;
+	float transmittance_curve = 1.0;
+	float transmittance_boost = 0.0;
+	float metallic = 0.0;
+	float specular = 0.5;
+	vec3 emission = vec3(0.0);
+	float roughness = 1.0;
+	float rim = 0.0;
+	float rim_tint = 0.0;
+	float clearcoat = 0.0;
+	float clearcoat_gloss = 0.0;
+	float anisotropy = 0.0;
+	vec2 anisotropy_flow = vec2(1.0, 0.0);
+	vec4 fog = vec4(0.0);
+#if defined(CUSTOM_RADIANCE_USED)
+	vec4 custom_radiance = vec4(0.0);
+#endif
+#if defined(CUSTOM_IRRADIANCE_USED)
+	vec4 custom_irradiance = vec4(0.0);
+#endif
+
+	float ao = 1.0;
+	float ao_light_affect = 0.0;
+
+	float alpha = 1.0;
+
+#if defined(TANGENT_USED) || defined(NORMAL_MAP_USED) || defined(LIGHT_ANISOTROPY_USED)
+	vec3 binormal = normalize(binormal_interp);
+	vec3 tangent = normalize(tangent_interp);
+#else
+	vec3 binormal = vec3(0.0);
+	vec3 tangent = vec3(0.0);
+#endif
+
+#ifdef NORMAL_USED
+	vec3 normal = normalize(normal_interp);
+
+#if defined(DO_SIDE_CHECK)
+	if (!gl_FrontFacing) {
+		normal = -normal;
+	}
+#endif
+
+#endif //NORMAL_USED
+
+#ifdef UV_USED
+	vec2 uv = uv_interp;
+#endif
+
+#if defined(UV2_USED) || defined(USE_LIGHTMAP)
+	vec2 uv2 = uv2_interp;
+#endif
+
+#if defined(COLOR_USED)
+	vec4 color = color_interp;
+#endif
+
+#if defined(NORMAL_MAP_USED)
+
+	vec3 normal_map = vec3(0.5);
+#endif
+
+	float normal_map_depth = 1.0;
+
+	vec2 screen_uv = gl_FragCoord.xy * scene_data.screen_pixel_size + scene_data.screen_pixel_size * 0.5; //account for center
+
+	float sss_strength = 0.0;
+
+#ifdef ALPHA_SCISSOR_USED
+	float alpha_scissor_threshold = 1.0;
+#endif // ALPHA_SCISSOR_USED
+
+#ifdef ALPHA_HASH_USED
+	float alpha_hash_scale = 1.0;
+#endif // ALPHA_HASH_USED
+
+#ifdef ALPHA_ANTIALIASING_EDGE_USED
+	float alpha_antialiasing_edge = 0.0;
+	vec2 alpha_texture_coordinate = vec2(0.0, 0.0);
+#endif // ALPHA_ANTIALIASING_EDGE_USED
+
+	{
+#CODE : FRAGMENT
+	}
+
+#ifdef LIGHT_TRANSMITTANCE_USED
+#ifdef SSS_MODE_SKIN
+	transmittance_color.a = sss_strength;
+#else
+	transmittance_color.a *= sss_strength;
+#endif
+#endif
+
+#ifndef USE_SHADOW_TO_OPACITY
+
+#ifdef ALPHA_SCISSOR_USED
+	if (alpha < alpha_scissor_threshold) {
+		discard;
+	}
+#endif // ALPHA_SCISSOR_USED
+
+// alpha hash can be used in unison with alpha antialiasing
+#ifdef ALPHA_HASH_USED
+	if (alpha < compute_alpha_hash_threshold(vertex, alpha_hash_scale)) {
+		discard;
+	}
+#endif // ALPHA_HASH_USED
+
+// If we are not edge antialiasing, we need to remove the output alpha channel from scissor and hash
+#if (defined(ALPHA_SCISSOR_USED) || defined(ALPHA_HASH_USED)) && !defined(ALPHA_ANTIALIASING_EDGE_USED)
+	alpha = 1.0;
+#endif
+
+#ifdef ALPHA_ANTIALIASING_EDGE_USED
+// If alpha scissor is used, we must further the edge threshold, otherwise we wont get any edge feather
+#ifdef ALPHA_SCISSOR_USED
+	alpha_antialiasing_edge = clamp(alpha_scissor_threshold + alpha_antialiasing_edge, 0.0, 1.0);
+#endif
+	alpha = compute_alpha_antialiasing_edge(alpha, alpha_texture_coordinate, alpha_antialiasing_edge);
+#endif // ALPHA_ANTIALIASING_EDGE_USED
+
+#ifdef USE_OPAQUE_PREPASS
+	if (alpha < opaque_prepass_threshold) {
+		discard;
+	}
+#endif // USE_OPAQUE_PREPASS
+
+#endif // !USE_SHADOW_TO_OPACITY
+
+#ifdef NORMAL_MAP_USED
+
+	normal_map.xy = normal_map.xy * 2.0 - 1.0;
+	normal_map.z = sqrt(max(0.0, 1.0 - dot(normal_map.xy, normal_map.xy))); //always ignore Z, as it can be RG packed, Z may be pos/neg, etc.
+
+	normal = normalize(mix(normal, tangent * normal_map.x + binormal * normal_map.y + normal * normal_map.z, normal_map_depth));
+
+#endif
+
+#ifdef LIGHT_ANISOTROPY_USED
+
+	if (anisotropy > 0.01) {
+		//rotation matrix
+		mat3 rot = mat3(tangent, binormal, normal);
+		//make local to space
+		tangent = normalize(rot * vec3(anisotropy_flow.x, anisotropy_flow.y, 0.0));
+		binormal = normalize(rot * vec3(-anisotropy_flow.y, anisotropy_flow.x, 0.0));
+	}
+
+#endif
+
+#ifdef ENABLE_CLIP_ALPHA
+	if (albedo.a < 0.99) {
+		//used for doublepass and shadowmapping
+		discard;
+	}
+#endif
+
+	/////////////////////// FOG //////////////////////
+#ifndef MODE_RENDER_DEPTH
+
+#ifndef CUSTOM_FOG_USED
+	// fog must be processed as early as possible and then packed.
+	// to maximize VGPR usage
+	// Draw "fixed" fog before volumetric fog to ensure volumetric fog can appear in front of the sky.
+
+	if (scene_data.fog_enabled) {
+		fog = fog_process(vertex);
+	}
+
+#endif //!CUSTOM_FOG_USED
+
+	uint fog_rg = packHalf2x16(fog.rg);
+	uint fog_ba = packHalf2x16(fog.ba);
+
+#endif //!MODE_RENDER_DEPTH
+
+	/////////////////////// DECALS ////////////////////////////////
+
+#ifndef MODE_RENDER_DEPTH
+
+	vec3 vertex_ddx = dFdx(vertex);
+	vec3 vertex_ddy = dFdy(vertex);
+
+	{ //Decals
+		// must implement
+
+		uint decal_indices = draw_call.decals.x;
+		for (uint i = 0; i < 8; i++) {
+			uint decal_index = decal_indices & 0xFF;
+			if (i == 4) {
+				decal_indices = draw_call.decals.y;
+			} else {
+				decal_indices = decal_indices >> 8;
+			}
+
+			if (decal_index == 0xFF) {
+				break;
+			}
+
+			vec3 uv_local = (decals.data[decal_index].xform * vec4(vertex, 1.0)).xyz;
+			if (any(lessThan(uv_local, vec3(0.0, -1.0, 0.0))) || any(greaterThan(uv_local, vec3(1.0)))) {
+				continue; //out of decal
+			}
+
+			//we need ddx/ddy for mipmaps, so simulate them
+			vec2 ddx = (decals.data[decal_index].xform * vec4(vertex_ddx, 0.0)).xz;
+			vec2 ddy = (decals.data[decal_index].xform * vec4(vertex_ddy, 0.0)).xz;
+
+			float fade = pow(1.0 - (uv_local.y > 0.0 ? uv_local.y : -uv_local.y), uv_local.y > 0.0 ? decals.data[decal_index].upper_fade : decals.data[decal_index].lower_fade);
+
+			if (decals.data[decal_index].normal_fade > 0.0) {
+				fade *= smoothstep(decals.data[decal_index].normal_fade, 1.0, dot(normal_interp, decals.data[decal_index].normal) * 0.5 + 0.5);
+			}
+
+			if (decals.data[decal_index].albedo_rect != vec4(0.0)) {
+				//has albedo
+				vec4 decal_albedo = textureGrad(sampler2D(decal_atlas_srgb, material_samplers[SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP]), uv_local.xz * decals.data[decal_index].albedo_rect.zw + decals.data[decal_index].albedo_rect.xy, ddx * decals.data[decal_index].albedo_rect.zw, ddy * decals.data[decal_index].albedo_rect.zw);
+				decal_albedo *= decals.data[decal_index].modulate;
+				decal_albedo.a *= fade;
+				albedo = mix(albedo, decal_albedo.rgb, decal_albedo.a * decals.data[decal_index].albedo_mix);
+
+				if (decals.data[decal_index].normal_rect != vec4(0.0)) {
+					vec3 decal_normal = textureGrad(sampler2D(decal_atlas, material_samplers[SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP]), uv_local.xz * decals.data[decal_index].normal_rect.zw + decals.data[decal_index].normal_rect.xy, ddx * decals.data[decal_index].normal_rect.zw, ddy * decals.data[decal_index].normal_rect.zw).xyz;
+					decal_normal.xy = decal_normal.xy * vec2(2.0, -2.0) - vec2(1.0, -1.0); //users prefer flipped y normal maps in most authoring software
+					decal_normal.z = sqrt(max(0.0, 1.0 - dot(decal_normal.xy, decal_normal.xy)));
+					//convert to view space, use xzy because y is up
+					decal_normal = (decals.data[decal_index].normal_xform * decal_normal.xzy).xyz;
+
+					normal = normalize(mix(normal, decal_normal, decal_albedo.a));
+				}
+
+				if (decals.data[decal_index].orm_rect != vec4(0.0)) {
+					vec3 decal_orm = textureGrad(sampler2D(decal_atlas, material_samplers[SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP]), uv_local.xz * decals.data[decal_index].orm_rect.zw + decals.data[decal_index].orm_rect.xy, ddx * decals.data[decal_index].orm_rect.zw, ddy * decals.data[decal_index].orm_rect.zw).xyz;
+					ao = mix(ao, decal_orm.r, decal_albedo.a);
+					roughness = mix(roughness, decal_orm.g, decal_albedo.a);
+					metallic = mix(metallic, decal_orm.b, decal_albedo.a);
+				}
+			}
+
+			if (decals.data[decal_index].emission_rect != vec4(0.0)) {
+				//emission is additive, so its independent from albedo
+				emission += textureGrad(sampler2D(decal_atlas_srgb, material_samplers[SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP]), uv_local.xz * decals.data[decal_index].emission_rect.zw + decals.data[decal_index].emission_rect.xy, ddx * decals.data[decal_index].emission_rect.zw, ddy * decals.data[decal_index].emission_rect.zw).xyz * decals.data[decal_index].emission_energy * fade;
+			}
+		}
+	} //Decals
+#endif //!MODE_RENDER_DEPTH
+
+	/////////////////////// LIGHTING //////////////////////////////
+
+#ifdef NORMAL_USED
+	if (scene_data.roughness_limiter_enabled) {
+		//http://www.jp.square-enix.com/tech/library/pdf/ImprovedGeometricSpecularAA.pdf
+		float roughness2 = roughness * roughness;
+		vec3 dndu = dFdx(normal), dndv = dFdx(normal);
+		float variance = scene_data.roughness_limiter_amount * (dot(dndu, dndu) + dot(dndv, dndv));
+		float kernelRoughness2 = min(2.0 * variance, scene_data.roughness_limiter_limit); //limit effect
+		float filteredRoughness2 = min(1.0, roughness2 + kernelRoughness2);
+		roughness = sqrt(filteredRoughness2);
+	}
+#endif // NORMAL_USED
+	//apply energy conservation
+
+	vec3 specular_light = vec3(0.0, 0.0, 0.0);
+	vec3 diffuse_light = vec3(0.0, 0.0, 0.0);
+	vec3 ambient_light = vec3(0.0, 0.0, 0.0);
+
+#if !defined(MODE_RENDER_DEPTH) && !defined(MODE_UNSHADED)
+
+	if (scene_data.use_reflection_cubemap) {
+		vec3 ref_vec = reflect(-view, normal);
+		ref_vec = scene_data.radiance_inverse_xform * ref_vec;
+#ifdef USE_RADIANCE_CUBEMAP_ARRAY
+
+		float lod, blend;
+		blend = modf(roughness * MAX_ROUGHNESS_LOD, lod);
+		specular_light = texture(samplerCubeArray(radiance_cubemap, material_samplers[SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP]), vec4(ref_vec, lod)).rgb;
+		specular_light = mix(specular_light, texture(samplerCubeArray(radiance_cubemap, material_samplers[SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP]), vec4(ref_vec, lod + 1)).rgb, blend);
+
+#else // USE_RADIANCE_CUBEMAP_ARRAY
+		specular_light = textureLod(samplerCube(radiance_cubemap, material_samplers[SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP]), ref_vec, roughness * MAX_ROUGHNESS_LOD).rgb;
+
+#endif //USE_RADIANCE_CUBEMAP_ARRAY
+		specular_light *= scene_data.ambient_light_color_energy.a;
+	}
+
+#if defined(CUSTOM_RADIANCE_USED)
+	specular_light = mix(specular_light, custom_radiance.rgb, custom_radiance.a);
+#endif // CUSTOM_RADIANCE_USED
+
+#ifndef USE_LIGHTMAP
+	//lightmap overrides everything
+	if (scene_data.use_ambient_light) {
+		ambient_light = scene_data.ambient_light_color_energy.rgb;
+
+		if (scene_data.use_ambient_cubemap) {
+			vec3 ambient_dir = scene_data.radiance_inverse_xform * normal;
+#ifdef USE_RADIANCE_CUBEMAP_ARRAY
+			vec3 cubemap_ambient = texture(samplerCubeArray(radiance_cubemap, material_samplers[SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP]), vec4(ambient_dir, MAX_ROUGHNESS_LOD)).rgb;
+#else
+			vec3 cubemap_ambient = textureLod(samplerCube(radiance_cubemap, material_samplers[SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP]), ambient_dir, MAX_ROUGHNESS_LOD).rgb;
+#endif //USE_RADIANCE_CUBEMAP_ARRAY
+
+			ambient_light = mix(ambient_light, cubemap_ambient * scene_data.ambient_light_color_energy.a, scene_data.ambient_color_sky_mix);
+		}
+	}
+#endif // !USE_LIGHTMAP
+
+#if defined(CUSTOM_IRRADIANCE_USED)
+	ambient_light = mix(specular_light, custom_irradiance.rgb, custom_irradiance.a);
+#endif // CUSTOM_IRRADIANCE_USED
+
+#endif //!defined(MODE_RENDER_DEPTH) && !defined(MODE_UNSHADED)
+
+	//radiance
+
+#if !defined(MODE_RENDER_DEPTH) && !defined(MODE_UNSHADED)
+
+#ifdef USE_LIGHTMAP
+
+	//lightmap
+	if (bool(draw_call.flags & INSTANCE_FLAGS_USE_LIGHTMAP_CAPTURE)) { //has lightmap capture
+		uint index = draw_call.gi_offset;
+
+		vec3 wnormal = mat3(scene_data.camera_matrix) * normal;
+		const float c1 = 0.429043;
+		const float c2 = 0.511664;
+		const float c3 = 0.743125;
+		const float c4 = 0.886227;
+		const float c5 = 0.247708;
+		ambient_light += (c1 * lightmap_captures.data[index].sh[8].rgb * (wnormal.x * wnormal.x - wnormal.y * wnormal.y) +
+						  c3 * lightmap_captures.data[index].sh[6].rgb * wnormal.z * wnormal.z +
+						  c4 * lightmap_captures.data[index].sh[0].rgb -
+						  c5 * lightmap_captures.data[index].sh[6].rgb +
+						  2.0 * c1 * lightmap_captures.data[index].sh[4].rgb * wnormal.x * wnormal.y +
+						  2.0 * c1 * lightmap_captures.data[index].sh[7].rgb * wnormal.x * wnormal.z +
+						  2.0 * c1 * lightmap_captures.data[index].sh[5].rgb * wnormal.y * wnormal.z +
+						  2.0 * c2 * lightmap_captures.data[index].sh[3].rgb * wnormal.x +
+						  2.0 * c2 * lightmap_captures.data[index].sh[1].rgb * wnormal.y +
+						  2.0 * c2 * lightmap_captures.data[index].sh[2].rgb * wnormal.z);
+
+	} else if (bool(draw_call.flags & INSTANCE_FLAGS_USE_LIGHTMAP)) { // has actual lightmap
+		bool uses_sh = bool(draw_call.flags & INSTANCE_FLAGS_USE_SH_LIGHTMAP);
+		uint ofs = draw_call.gi_offset & 0xFFFF;
+		vec3 uvw;
+		uvw.xy = uv2 * draw_call.lightmap_uv_scale.zw + draw_call.lightmap_uv_scale.xy;
+		uvw.z = float((draw_call.gi_offset >> 16) & 0xFFFF);
+
+		if (uses_sh) {
+			uvw.z *= 4.0; //SH textures use 4 times more data
+			vec3 lm_light_l0 = textureLod(sampler2DArray(lightmap_textures[ofs], material_samplers[SAMPLER_LINEAR_CLAMP]), uvw + vec3(0.0, 0.0, 0.0), 0.0).rgb;
+			vec3 lm_light_l1n1 = textureLod(sampler2DArray(lightmap_textures[ofs], material_samplers[SAMPLER_LINEAR_CLAMP]), uvw + vec3(0.0, 0.0, 1.0), 0.0).rgb;
+			vec3 lm_light_l1_0 = textureLod(sampler2DArray(lightmap_textures[ofs], material_samplers[SAMPLER_LINEAR_CLAMP]), uvw + vec3(0.0, 0.0, 2.0), 0.0).rgb;
+			vec3 lm_light_l1p1 = textureLod(sampler2DArray(lightmap_textures[ofs], material_samplers[SAMPLER_LINEAR_CLAMP]), uvw + vec3(0.0, 0.0, 3.0), 0.0).rgb;
+
+			uint idx = draw_call.gi_offset >> 20;
+			vec3 n = normalize(lightmaps.data[idx].normal_xform * normal);
+
+			ambient_light += lm_light_l0 * 0.282095f;
+			ambient_light += lm_light_l1n1 * 0.32573 * n.y;
+			ambient_light += lm_light_l1_0 * 0.32573 * n.z;
+			ambient_light += lm_light_l1p1 * 0.32573 * n.x;
+			if (metallic > 0.01) { // since the more direct bounced light is lost, we can kind of fake it with this trick
+				vec3 r = reflect(normalize(-vertex), normal);
+				specular_light += lm_light_l1n1 * 0.32573 * r.y;
+				specular_light += lm_light_l1_0 * 0.32573 * r.z;
+				specular_light += lm_light_l1p1 * 0.32573 * r.x;
+			}
+
+		} else {
+			ambient_light += textureLod(sampler2DArray(lightmap_textures[ofs], material_samplers[SAMPLER_LINEAR_CLAMP]), uvw, 0.0).rgb;
+		}
+	}
+
+	// No GI nor non low end mode...
+
+#endif // USE_LIGHTMAP
+
+	// skipping ssao, do we remove ssao totally?
+
+	{ //Reflection probes
+		vec4 reflection_accum = vec4(0.0, 0.0, 0.0, 0.0);
+		vec4 ambient_accum = vec4(0.0, 0.0, 0.0, 0.0);
+
+		uint reflection_indices = draw_call.reflection_probes.x;
+		for (uint i = 0; i < 8; i++) {
+			uint reflection_index = reflection_indices & 0xFF;
+			if (i == 4) {
+				reflection_indices = draw_call.reflection_probes.y;
+			} else {
+				reflection_indices = reflection_indices >> 8;
+			}
+
+			if (reflection_index == 0xFF) {
+				break;
+			}
+
+			reflection_process(reflection_index, vertex, normal, roughness, ambient_light, specular_light, ambient_accum, reflection_accum);
+		}
+
+		if (reflection_accum.a > 0.0) {
+			specular_light = reflection_accum.rgb / reflection_accum.a;
+		}
+	} //Reflection probes
+
+	// finalize ambient light here
+	ambient_light *= albedo.rgb;
+	ambient_light *= ao;
+
+	// convert ao to direct light ao
+	ao = mix(1.0, ao, ao_light_affect);
+
+	//this saves some VGPRs
+	vec3 f0 = F0(metallic, specular, albedo);
+
+	{
+#if defined(DIFFUSE_TOON)
+		//simplify for toon, as
+		specular_light *= specular * metallic * albedo * 2.0;
+#else
+
+		// scales the specular reflections, needs to be be computed before lighting happens,
+		// but after environment, GI, and reflection probes are added
+		// Environment brdf approximation (Lazarov 2013)
+		// see https://www.unrealengine.com/en-US/blog/physically-based-shading-on-mobile
+		const vec4 c0 = vec4(-1.0, -0.0275, -0.572, 0.022);
+		const vec4 c1 = vec4(1.0, 0.0425, 1.04, -0.04);
+		vec4 r = roughness * c0 + c1;
+		float ndotv = clamp(dot(normal, view), 0.0, 1.0);
+		float a004 = min(r.x * r.x, exp2(-9.28 * ndotv)) * r.x + r.y;
+		vec2 env = vec2(-1.04, 1.04) * a004 + r.zw;
+
+		specular_light *= env.x * f0 + env.y;
+#endif
+	}
+
+#endif // !defined(MODE_RENDER_DEPTH) && !defined(MODE_UNSHADED)
+
+#if !defined(MODE_RENDER_DEPTH)
+	//this saves some VGPRs
+	uint orms = packUnorm4x8(vec4(ao, roughness, metallic, specular));
+#endif
+
+// LIGHTING
+#if !defined(MODE_RENDER_DEPTH) && !defined(MODE_UNSHADED)
+
+	{ //directional light
+
+		// Do shadow and lighting in two passes to reduce register pressure
+		uint shadow0 = 0;
+		uint shadow1 = 0;
+
+		for (uint i = 0; i < 8; i++) {
+			if (i >= scene_data.directional_light_count) {
+				break;
+			}
+
+			if (!bool(directional_lights.data[i].mask & draw_call.layer_mask)) {
+				continue; //not masked
+			}
+
+			float shadow = 1.0;
+
+			// Directional light shadow code is basically the same as forward clustered at this point in time minus `LIGHT_TRANSMITTANCE_USED` support.
+			// Not sure if there is a reason to change this seeing directional lights are part of our global data
+			// Should think about whether we may want to move this code into an include file or function??
+
+#ifdef USE_SOFT_SHADOWS
+			//version with soft shadows, more expensive
+			if (directional_lights.data[i].shadow_enabled) {
+				float depth_z = -vertex.z;
+
+				vec4 pssm_coord;
+				vec3 shadow_color = vec3(0.0);
+				vec3 light_dir = directional_lights.data[i].direction;
+
+#define BIAS_FUNC(m_var, m_idx)                                                                                                                                       \
+	m_var.xyz += light_dir * directional_lights.data[i].shadow_bias[m_idx];                                                                                           \
+	vec3 normal_bias = normalize(normal_interp) * (1.0 - max(0.0, dot(light_dir, -normalize(normal_interp)))) * directional_lights.data[i].shadow_normal_bias[m_idx]; \
+	normal_bias -= light_dir * dot(light_dir, normal_bias);                                                                                                           \
+	m_var.xyz += normal_bias;
+
+				if (depth_z < directional_lights.data[i].shadow_split_offsets.x) {
+					vec4 v = vec4(vertex, 1.0);
+
+					BIAS_FUNC(v, 0)
+
+					pssm_coord = (directional_lights.data[i].shadow_matrix1 * v);
+					pssm_coord /= pssm_coord.w;
+
+					if (directional_lights.data[i].softshadow_angle > 0) {
+						float range_pos = dot(directional_lights.data[i].direction, v.xyz);
+						float range_begin = directional_lights.data[i].shadow_range_begin.x;
+						float test_radius = (range_pos - range_begin) * directional_lights.data[i].softshadow_angle;
+						vec2 tex_scale = directional_lights.data[i].uv_scale1 * test_radius;
+						shadow = sample_directional_soft_shadow(directional_shadow_atlas, pssm_coord.xyz, tex_scale * directional_lights.data[i].soft_shadow_scale);
+					} else {
+						shadow = sample_directional_pcf_shadow(directional_shadow_atlas, scene_data.directional_shadow_pixel_size * directional_lights.data[i].soft_shadow_scale, pssm_coord);
+					}
+
+					shadow_color = directional_lights.data[i].shadow_color1.rgb;
+
+				} else if (depth_z < directional_lights.data[i].shadow_split_offsets.y) {
+					vec4 v = vec4(vertex, 1.0);
+
+					BIAS_FUNC(v, 1)
+
+					pssm_coord = (directional_lights.data[i].shadow_matrix2 * v);
+					pssm_coord /= pssm_coord.w;
+
+					if (directional_lights.data[i].softshadow_angle > 0) {
+						float range_pos = dot(directional_lights.data[i].direction, v.xyz);
+						float range_begin = directional_lights.data[i].shadow_range_begin.y;
+						float test_radius = (range_pos - range_begin) * directional_lights.data[i].softshadow_angle;
+						vec2 tex_scale = directional_lights.data[i].uv_scale2 * test_radius;
+						shadow = sample_directional_soft_shadow(directional_shadow_atlas, pssm_coord.xyz, tex_scale * directional_lights.data[i].soft_shadow_scale);
+					} else {
+						shadow = sample_directional_pcf_shadow(directional_shadow_atlas, scene_data.directional_shadow_pixel_size * directional_lights.data[i].soft_shadow_scale, pssm_coord);
+					}
+
+					shadow_color = directional_lights.data[i].shadow_color2.rgb;
+				} else if (depth_z < directional_lights.data[i].shadow_split_offsets.z) {
+					vec4 v = vec4(vertex, 1.0);
+
+					BIAS_FUNC(v, 2)
+
+					pssm_coord = (directional_lights.data[i].shadow_matrix3 * v);
+					pssm_coord /= pssm_coord.w;
+
+					if (directional_lights.data[i].softshadow_angle > 0) {
+						float range_pos = dot(directional_lights.data[i].direction, v.xyz);
+						float range_begin = directional_lights.data[i].shadow_range_begin.z;
+						float test_radius = (range_pos - range_begin) * directional_lights.data[i].softshadow_angle;
+						vec2 tex_scale = directional_lights.data[i].uv_scale3 * test_radius;
+						shadow = sample_directional_soft_shadow(directional_shadow_atlas, pssm_coord.xyz, tex_scale * directional_lights.data[i].soft_shadow_scale);
+					} else {
+						shadow = sample_directional_pcf_shadow(directional_shadow_atlas, scene_data.directional_shadow_pixel_size * directional_lights.data[i].soft_shadow_scale, pssm_coord);
+					}
+
+					shadow_color = directional_lights.data[i].shadow_color3.rgb;
+
+				} else {
+					vec4 v = vec4(vertex, 1.0);
+
+					BIAS_FUNC(v, 3)
+
+					pssm_coord = (directional_lights.data[i].shadow_matrix4 * v);
+					pssm_coord /= pssm_coord.w;
+
+					if (directional_lights.data[i].softshadow_angle > 0) {
+						float range_pos = dot(directional_lights.data[i].direction, v.xyz);
+						float range_begin = directional_lights.data[i].shadow_range_begin.w;
+						float test_radius = (range_pos - range_begin) * directional_lights.data[i].softshadow_angle;
+						vec2 tex_scale = directional_lights.data[i].uv_scale4 * test_radius;
+						shadow = sample_directional_soft_shadow(directional_shadow_atlas, pssm_coord.xyz, tex_scale * directional_lights.data[i].soft_shadow_scale);
+					} else {
+						shadow = sample_directional_pcf_shadow(directional_shadow_atlas, scene_data.directional_shadow_pixel_size * directional_lights.data[i].soft_shadow_scale, pssm_coord);
+					}
+
+					shadow_color = directional_lights.data[i].shadow_color4.rgb;
+				}
+
+				if (directional_lights.data[i].blend_splits) {
+					vec3 shadow_color_blend = vec3(0.0);
+					float pssm_blend;
+					float shadow2;
+
+					if (depth_z < directional_lights.data[i].shadow_split_offsets.x) {
+						vec4 v = vec4(vertex, 1.0);
+						BIAS_FUNC(v, 1)
+						pssm_coord = (directional_lights.data[i].shadow_matrix2 * v);
+						pssm_coord /= pssm_coord.w;
+
+						if (directional_lights.data[i].softshadow_angle > 0) {
+							float range_pos = dot(directional_lights.data[i].direction, v.xyz);
+							float range_begin = directional_lights.data[i].shadow_range_begin.y;
+							float test_radius = (range_pos - range_begin) * directional_lights.data[i].softshadow_angle;
+							vec2 tex_scale = directional_lights.data[i].uv_scale2 * test_radius;
+							shadow2 = sample_directional_soft_shadow(directional_shadow_atlas, pssm_coord.xyz, tex_scale * directional_lights.data[i].soft_shadow_scale);
+						} else {
+							shadow2 = sample_directional_pcf_shadow(directional_shadow_atlas, scene_data.directional_shadow_pixel_size * directional_lights.data[i].soft_shadow_scale, pssm_coord);
+						}
+
+						pssm_blend = smoothstep(0.0, directional_lights.data[i].shadow_split_offsets.x, depth_z);
+						shadow_color_blend = directional_lights.data[i].shadow_color2.rgb;
+					} else if (depth_z < directional_lights.data[i].shadow_split_offsets.y) {
+						vec4 v = vec4(vertex, 1.0);
+						BIAS_FUNC(v, 2)
+						pssm_coord = (directional_lights.data[i].shadow_matrix3 * v);
+						pssm_coord /= pssm_coord.w;
+
+						if (directional_lights.data[i].softshadow_angle > 0) {
+							float range_pos = dot(directional_lights.data[i].direction, v.xyz);
+							float range_begin = directional_lights.data[i].shadow_range_begin.z;
+							float test_radius = (range_pos - range_begin) * directional_lights.data[i].softshadow_angle;
+							vec2 tex_scale = directional_lights.data[i].uv_scale3 * test_radius;
+							shadow2 = sample_directional_soft_shadow(directional_shadow_atlas, pssm_coord.xyz, tex_scale * directional_lights.data[i].soft_shadow_scale);
+						} else {
+							shadow2 = sample_directional_pcf_shadow(directional_shadow_atlas, scene_data.directional_shadow_pixel_size * directional_lights.data[i].soft_shadow_scale, pssm_coord);
+						}
+
+						pssm_blend = smoothstep(directional_lights.data[i].shadow_split_offsets.x, directional_lights.data[i].shadow_split_offsets.y, depth_z);
+
+						shadow_color_blend = directional_lights.data[i].shadow_color3.rgb;
+					} else if (depth_z < directional_lights.data[i].shadow_split_offsets.z) {
+						vec4 v = vec4(vertex, 1.0);
+						BIAS_FUNC(v, 3)
+						pssm_coord = (directional_lights.data[i].shadow_matrix4 * v);
+						pssm_coord /= pssm_coord.w;
+						if (directional_lights.data[i].softshadow_angle > 0) {
+							float range_pos = dot(directional_lights.data[i].direction, v.xyz);
+							float range_begin = directional_lights.data[i].shadow_range_begin.w;
+							float test_radius = (range_pos - range_begin) * directional_lights.data[i].softshadow_angle;
+							vec2 tex_scale = directional_lights.data[i].uv_scale4 * test_radius;
+							shadow2 = sample_directional_soft_shadow(directional_shadow_atlas, pssm_coord.xyz, tex_scale * directional_lights.data[i].soft_shadow_scale);
+						} else {
+							shadow2 = sample_directional_pcf_shadow(directional_shadow_atlas, scene_data.directional_shadow_pixel_size * directional_lights.data[i].soft_shadow_scale, pssm_coord);
+						}
+
+						pssm_blend = smoothstep(directional_lights.data[i].shadow_split_offsets.y, directional_lights.data[i].shadow_split_offsets.z, depth_z);
+						shadow_color_blend = directional_lights.data[i].shadow_color4.rgb;
+					} else {
+						pssm_blend = 0.0; //if no blend, same coord will be used (divide by z will result in same value, and already cached)
+					}
+
+					pssm_blend = sqrt(pssm_blend);
+
+					shadow = mix(shadow, shadow2, pssm_blend);
+					shadow_color = mix(shadow_color, shadow_color_blend, pssm_blend);
+				}
+
+				shadow = mix(shadow, 1.0, smoothstep(directional_lights.data[i].fade_from, directional_lights.data[i].fade_to, vertex.z)); //done with negative values for performance
+
+#undef BIAS_FUNC
+			}
+#else
+			// Soft shadow disabled version
+
+			if (directional_lights.data[i].shadow_enabled) {
+				float depth_z = -vertex.z;
+
+				vec4 pssm_coord;
+				vec3 light_dir = directional_lights.data[i].direction;
+				vec3 base_normal_bias = normalize(normal_interp) * (1.0 - max(0.0, dot(light_dir, -normalize(normal_interp))));
+
+#define BIAS_FUNC(m_var, m_idx)                                                                 \
+	m_var.xyz += light_dir * directional_lights.data[i].shadow_bias[m_idx];                     \
+	vec3 normal_bias = base_normal_bias * directional_lights.data[i].shadow_normal_bias[m_idx]; \
+	normal_bias -= light_dir * dot(light_dir, normal_bias);                                     \
+	m_var.xyz += normal_bias;
+
+				if (depth_z < directional_lights.data[i].shadow_split_offsets.x) {
+					vec4 v = vec4(vertex, 1.0);
+
+					BIAS_FUNC(v, 0)
+
+					pssm_coord = (directional_lights.data[i].shadow_matrix1 * v);
+				} else if (depth_z < directional_lights.data[i].shadow_split_offsets.y) {
+					vec4 v = vec4(vertex, 1.0);
+
+					BIAS_FUNC(v, 1)
+
+					pssm_coord = (directional_lights.data[i].shadow_matrix2 * v);
+				} else if (depth_z < directional_lights.data[i].shadow_split_offsets.z) {
+					vec4 v = vec4(vertex, 1.0);
+
+					BIAS_FUNC(v, 2)
+
+					pssm_coord = (directional_lights.data[i].shadow_matrix3 * v);
+
+				} else {
+					vec4 v = vec4(vertex, 1.0);
+
+					BIAS_FUNC(v, 3)
+
+					pssm_coord = (directional_lights.data[i].shadow_matrix4 * v);
+				}
+
+				pssm_coord /= pssm_coord.w;
+
+				shadow = sample_directional_pcf_shadow(directional_shadow_atlas, scene_data.directional_shadow_pixel_size * directional_lights.data[i].soft_shadow_scale, pssm_coord);
+
+				if (directional_lights.data[i].blend_splits) {
+					float pssm_blend;
+
+					if (depth_z < directional_lights.data[i].shadow_split_offsets.x) {
+						vec4 v = vec4(vertex, 1.0);
+						BIAS_FUNC(v, 1)
+						pssm_coord = (directional_lights.data[i].shadow_matrix2 * v);
+						pssm_blend = smoothstep(0.0, directional_lights.data[i].shadow_split_offsets.x, depth_z);
+					} else if (depth_z < directional_lights.data[i].shadow_split_offsets.y) {
+						vec4 v = vec4(vertex, 1.0);
+						BIAS_FUNC(v, 2)
+						pssm_coord = (directional_lights.data[i].shadow_matrix3 * v);
+						pssm_blend = smoothstep(directional_lights.data[i].shadow_split_offsets.x, directional_lights.data[i].shadow_split_offsets.y, depth_z);
+					} else if (depth_z < directional_lights.data[i].shadow_split_offsets.z) {
+						vec4 v = vec4(vertex, 1.0);
+						BIAS_FUNC(v, 3)
+						pssm_coord = (directional_lights.data[i].shadow_matrix4 * v);
+						pssm_blend = smoothstep(directional_lights.data[i].shadow_split_offsets.y, directional_lights.data[i].shadow_split_offsets.z, depth_z);
+					} else {
+						pssm_blend = 0.0; //if no blend, same coord will be used (divide by z will result in same value, and already cached)
+					}
+
+					pssm_coord /= pssm_coord.w;
+
+					float shadow2 = sample_directional_pcf_shadow(directional_shadow_atlas, scene_data.directional_shadow_pixel_size * directional_lights.data[i].soft_shadow_scale, pssm_coord);
+					shadow = mix(shadow, shadow2, pssm_blend);
+				}
+
+				shadow = mix(shadow, 1.0, smoothstep(directional_lights.data[i].fade_from, directional_lights.data[i].fade_to, vertex.z)); //done with negative values for performance
+
+#undef BIAS_FUNC
+			}
+#endif
+
+			if (i < 4) {
+				shadow0 |= uint(clamp(shadow * 255.0, 0.0, 255.0)) << (i * 8);
+			} else {
+				shadow1 |= uint(clamp(shadow * 255.0, 0.0, 255.0)) << ((i - 4) * 8);
+			}
+		}
+
+		for (uint i = 0; i < 8; i++) {
+			if (i >= scene_data.directional_light_count) {
+				break;
+			}
+
+			if (!bool(directional_lights.data[i].mask & draw_call.layer_mask)) {
+				continue; //not masked
+			}
+
+			// We're not doing light transmittence
+
+			float shadow = 1.0;
+
+			if (i < 4) {
+				shadow = float(shadow0 >> (i * 8) & 0xFF) / 255.0;
+			} else {
+				shadow = float(shadow1 >> ((i - 4) * 8) & 0xFF) / 255.0;
+			}
+
+			blur_shadow(shadow);
+
+			light_compute(normal, directional_lights.data[i].direction, normalize(view), directional_lights.data[i].color * directional_lights.data[i].energy, shadow, f0, orms, 1.0,
+#ifdef LIGHT_BACKLIGHT_USED
+					backlight,
+#endif
+/* not supported here
+#ifdef LIGHT_TRANSMITTANCE_USED
+					transmittance_color,
+					transmittance_depth,
+					transmittance_curve,
+					transmittance_boost,
+					transmittance_z,
+#endif
+*/
+#ifdef LIGHT_RIM_USED
+					rim, rim_tint, albedo,
+#endif
+#ifdef LIGHT_CLEARCOAT_USED
+					clearcoat, clearcoat_gloss,
+#endif
+#ifdef LIGHT_ANISOTROPY_USED
+					binormal, tangent, anisotropy,
+#endif
+#ifdef USE_SOFT_SHADOW
+					directional_lights.data[i].size,
+#endif
+#ifdef USE_SHADOW_TO_OPACITY
+					alpha,
+#endif
+					diffuse_light,
+					specular_light);
+		}
+	} //directional light
+
+	{ //omni lights
+		uint light_indices = draw_call.omni_lights.x;
+		for (uint i = 0; i < 8; i++) {
+			uint light_index = light_indices & 0xFF;
+			if (i == 4) {
+				light_indices = draw_call.omni_lights.y;
+			} else {
+				light_indices = light_indices >> 8;
+			}
+
+			if (light_index == 0xFF) {
+				break;
+			}
+
+			float shadow = light_process_omni_shadow(light_index, vertex, view);
+
+			shadow = blur_shadow(shadow);
+
+			light_process_omni(light_index, vertex, view, normal, vertex_ddx, vertex_ddy, f0, orms, shadow,
+#ifdef LIGHT_BACKLIGHT_USED
+					backlight,
+#endif
+/*
+#ifdef LIGHT_TRANSMITTANCE_USED
+					transmittance_color,
+					transmittance_depth,
+					transmittance_curve,
+					transmittance_boost,
+#endif
+*/
+#ifdef LIGHT_RIM_USED
+					rim,
+					rim_tint,
+					albedo,
+#endif
+#ifdef LIGHT_CLEARCOAT_USED
+					clearcoat, clearcoat_gloss,
+#endif
+#ifdef LIGHT_ANISOTROPY_USED
+					tangent, binormal, anisotropy,
+#endif
+#ifdef USE_SHADOW_TO_OPACITY
+					alpha,
+#endif
+					diffuse_light, specular_light);
+		}
+	} //omni lights
+
+	{ //spot lights
+
+		uint light_indices = draw_call.spot_lights.x;
+		for (uint i = 0; i < 8; i++) {
+			uint light_index = light_indices & 0xFF;
+			if (i == 4) {
+				light_indices = draw_call.spot_lights.y;
+			} else {
+				light_indices = light_indices >> 8;
+			}
+
+			if (light_index == 0xFF) {
+				break;
+			}
+
+			float shadow = light_process_spot_shadow(light_index, vertex, view);
+
+			shadow = blur_shadow(shadow);
+
+			light_process_spot(light_index, vertex, view, normal, vertex_ddx, vertex_ddy, f0, orms, shadow,
+#ifdef LIGHT_BACKLIGHT_USED
+					backlight,
+#endif
+/*
+#ifdef LIGHT_TRANSMITTANCE_USED
+					transmittance_color,
+					transmittance_depth,
+					transmittance_curve,
+					transmittance_boost,
+#endif
+*/
+#ifdef LIGHT_RIM_USED
+					rim,
+					rim_tint,
+					albedo,
+#endif
+#ifdef LIGHT_CLEARCOAT_USED
+					clearcoat, clearcoat_gloss,
+#endif
+#ifdef LIGHT_ANISOTROPY_USED
+					tangent, binormal, anisotropy,
+#endif
+#ifdef USE_SHADOW_TO_OPACITY
+					alpha,
+#endif
+					diffuse_light, specular_light);
+		}
+	} //spot lights
+
+#ifdef USE_SHADOW_TO_OPACITY
+	alpha = min(alpha, clamp(length(ambient_light), 0.0, 1.0));
+
+#if defined(ALPHA_SCISSOR_USED)
+	if (alpha < alpha_scissor) {
+		discard;
+	}
+#endif // ALPHA_SCISSOR_USED
+
+#ifdef USE_OPAQUE_PREPASS
+
+	if (alpha < opaque_prepass_threshold) {
+		discard;
+	}
+
+#endif // USE_OPAQUE_PREPASS
+
+#endif // USE_SHADOW_TO_OPACITY
+
+#endif //!defined(MODE_RENDER_DEPTH) && !defined(MODE_UNSHADED)
+
+#ifdef MODE_RENDER_DEPTH
+
+#ifdef MODE_RENDER_MATERIAL
+
+	albedo_output_buffer.rgb = albedo;
+	albedo_output_buffer.a = alpha;
+
+	normal_output_buffer.rgb = normal * 0.5 + 0.5;
+	normal_output_buffer.a = 0.0;
+	depth_output_buffer.r = -vertex.z;
+
+	orm_output_buffer.r = ao;
+	orm_output_buffer.g = roughness;
+	orm_output_buffer.b = metallic;
+	orm_output_buffer.a = sss_strength;
+
+	emission_output_buffer.rgb = emission;
+	emission_output_buffer.a = 0.0;
+#endif // MODE_RENDER_MATERIAL
+
+#else // MODE_RENDER_DEPTH
+
+	// multiply by albedo
+	diffuse_light *= albedo; // ambient must be multiplied by albedo at the end
+
+	// apply direct light AO
+	ao = unpackUnorm4x8(orms).x;
+	specular_light *= ao;
+	diffuse_light *= ao;
+
+	// apply metallic
+	metallic = unpackUnorm4x8(orms).z;
+	diffuse_light *= 1.0 - metallic;
+	ambient_light *= 1.0 - metallic;
+
+	//restore fog
+	fog = vec4(unpackHalf2x16(fog_rg), unpackHalf2x16(fog_ba));
+
+#ifdef MODE_MULTIPLE_RENDER_TARGETS
+
+#ifdef MODE_UNSHADED
+	diffuse_buffer = vec4(albedo.rgb, 0.0);
+	specular_buffer = vec4(0.0);
+
+#else // MODE_UNSHADED
+
+#ifdef SSS_MODE_SKIN
+	sss_strength = -sss_strength;
+#endif // SSS_MODE_SKIN
+	diffuse_buffer = vec4(emission + diffuse_light + ambient_light, sss_strength);
+	specular_buffer = vec4(specular_light, metallic);
+#endif // MODE_UNSHADED
+
+	diffuse_buffer.rgb = mix(diffuse_buffer.rgb, fog.rgb, fog.a);
+	specular_buffer.rgb = mix(specular_buffer.rgb, vec3(0.0), fog.a);
+
+#else //MODE_MULTIPLE_RENDER_TARGETS
+
+#ifdef MODE_UNSHADED
+	frag_color = vec4(albedo, alpha);
+#else // MODE_UNSHADED
+	frag_color = vec4(emission + ambient_light + diffuse_light + specular_light, alpha);
+	//frag_color = vec4(1.0);
+#endif // MODE_UNSHADED
+
+	// Draw "fixed" fog before volumetric fog to ensure volumetric fog can appear in front of the sky.
+	frag_color.rgb = mix(frag_color.rgb, fog.rgb, fog.a);
+
+#endif //MODE_MULTIPLE_RENDER_TARGETS
+
+#endif //MODE_RENDER_DEPTH
+}

+ 220 - 0
servers/rendering/renderer_rd/shaders/scene_forward_mobile_inc.glsl

@@ -0,0 +1,220 @@
+#define M_PI 3.14159265359
+
+#include "decal_data_inc.glsl"
+
+#if !defined(MODE_RENDER_DEPTH) || defined(MODE_RENDER_MATERIAL) || defined(TANGENT_USED) || defined(NORMAL_MAP_USED)
+#ifndef NORMAL_USED
+#define NORMAL_USED
+#endif
+#endif
+
+/* don't exceed 128 bytes!! */
+/* put instance data into our push content, not a array */
+layout(push_constant, binding = 0, std430) uniform DrawCall {
+	mat4 transform; // 64 - 64
+	uint flags; // 04 - 68
+	uint instance_uniforms_ofs; //base offset in global buffer for instance variables	// 04 - 72
+	uint gi_offset; //GI information when using lightmapping (VCT or lightmap index)    // 04 - 76
+	uint layer_mask; // 04 - 80
+	vec4 lightmap_uv_scale; // 16 - 96 doubles as uv_offset when needed
+
+	uvec2 reflection_probes; // 08 - 104
+	uvec2 omni_lights; // 08 - 112
+	uvec2 spot_lights; // 08 - 120
+	uvec2 decals; // 08 - 128
+}
+draw_call;
+
+/* Set 0: Base Pass (never changes) */
+
+#include "light_data_inc.glsl"
+
+#define SAMPLER_NEAREST_CLAMP 0
+#define SAMPLER_LINEAR_CLAMP 1
+#define SAMPLER_NEAREST_WITH_MIPMAPS_CLAMP 2
+#define SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP 3
+#define SAMPLER_NEAREST_WITH_MIPMAPS_ANISOTROPIC_CLAMP 4
+#define SAMPLER_LINEAR_WITH_MIPMAPS_ANISOTROPIC_CLAMP 5
+#define SAMPLER_NEAREST_REPEAT 6
+#define SAMPLER_LINEAR_REPEAT 7
+#define SAMPLER_NEAREST_WITH_MIPMAPS_REPEAT 8
+#define SAMPLER_LINEAR_WITH_MIPMAPS_REPEAT 9
+#define SAMPLER_NEAREST_WITH_MIPMAPS_ANISOTROPIC_REPEAT 10
+#define SAMPLER_LINEAR_WITH_MIPMAPS_ANISOTROPIC_REPEAT 11
+
+layout(set = 0, binding = 1) uniform sampler material_samplers[12];
+
+layout(set = 0, binding = 2) uniform sampler shadow_sampler;
+
+#define INSTANCE_FLAGS_USE_GI_BUFFERS (1 << 6)
+#define INSTANCE_FLAGS_USE_SDFGI (1 << 7)
+#define INSTANCE_FLAGS_USE_LIGHTMAP_CAPTURE (1 << 8)
+#define INSTANCE_FLAGS_USE_LIGHTMAP (1 << 9)
+#define INSTANCE_FLAGS_USE_SH_LIGHTMAP (1 << 10)
+#define INSTANCE_FLAGS_USE_GIPROBE (1 << 11)
+#define INSTANCE_FLAGS_MULTIMESH (1 << 12)
+#define INSTANCE_FLAGS_MULTIMESH_FORMAT_2D (1 << 13)
+#define INSTANCE_FLAGS_MULTIMESH_HAS_COLOR (1 << 14)
+#define INSTANCE_FLAGS_MULTIMESH_HAS_CUSTOM_DATA (1 << 15)
+#define INSTANCE_FLAGS_PARTICLE_TRAIL_SHIFT 16
+//3 bits of stride
+#define INSTANCE_FLAGS_PARTICLE_TRAIL_MASK 0xFF
+
+#define INSTANCE_FLAGS_NON_UNIFORM_SCALE (1 << 24)
+
+layout(set = 0, binding = 3, std430) restrict readonly buffer OmniLights {
+	LightData data[];
+}
+omni_lights;
+
+layout(set = 0, binding = 4, std430) restrict readonly buffer SpotLights {
+	LightData data[];
+}
+spot_lights;
+
+layout(set = 0, binding = 5, std430) restrict readonly buffer ReflectionProbeData {
+	ReflectionData data[];
+}
+reflections;
+
+layout(set = 0, binding = 6, std140) uniform DirectionalLights {
+	DirectionalLightData data[MAX_DIRECTIONAL_LIGHT_DATA_STRUCTS];
+}
+directional_lights;
+
+#define LIGHTMAP_FLAG_USE_DIRECTION 1
+#define LIGHTMAP_FLAG_USE_SPECULAR_DIRECTION 2
+
+struct Lightmap {
+	mat3 normal_xform;
+};
+
+layout(set = 0, binding = 7, std140) restrict readonly buffer Lightmaps {
+	Lightmap data[];
+}
+lightmaps;
+
+struct LightmapCapture {
+	vec4 sh[9];
+};
+
+layout(set = 0, binding = 8, std140) restrict readonly buffer LightmapCaptures {
+	LightmapCapture data[];
+}
+lightmap_captures;
+
+layout(set = 0, binding = 9) uniform texture2D decal_atlas;
+layout(set = 0, binding = 10) uniform texture2D decal_atlas_srgb;
+
+layout(set = 0, binding = 11, std430) restrict readonly buffer Decals {
+	DecalData data[];
+}
+decals;
+
+layout(set = 0, binding = 12, std430) restrict readonly buffer GlobalVariableData {
+	vec4 data[];
+}
+global_variables;
+
+/* Set 1: Render Pass (changes per render pass) */
+
+layout(set = 1, binding = 0, std140) uniform SceneData {
+	mat4 projection_matrix;
+	mat4 inv_projection_matrix;
+
+	mat4 camera_matrix;
+	mat4 inv_camera_matrix;
+
+	vec2 viewport_size;
+	vec2 screen_pixel_size;
+
+	//use vec4s because std140 doesnt play nice with vec2s, z and w are wasted
+	vec4 directional_penumbra_shadow_kernel[32];
+	vec4 directional_soft_shadow_kernel[32];
+	vec4 penumbra_shadow_kernel[32];
+	vec4 soft_shadow_kernel[32];
+
+	uint directional_penumbra_shadow_samples;
+	uint directional_soft_shadow_samples;
+	uint penumbra_shadow_samples;
+	uint soft_shadow_samples;
+
+	vec4 ambient_light_color_energy;
+
+	float ambient_color_sky_mix;
+	bool use_ambient_light;
+	bool use_ambient_cubemap;
+	bool use_reflection_cubemap;
+
+	mat3 radiance_inverse_xform;
+
+	vec2 shadow_atlas_pixel_size;
+	vec2 directional_shadow_pixel_size;
+
+	uint directional_light_count;
+	float dual_paraboloid_side;
+	float z_far;
+	float z_near;
+
+	bool ssao_enabled;
+	float ssao_light_affect;
+	float ssao_ao_affect;
+	bool roughness_limiter_enabled;
+
+	float roughness_limiter_amount;
+	float roughness_limiter_limit;
+	uvec2 roughness_limiter_pad;
+
+	vec4 ao_color;
+
+	bool fog_enabled;
+	float fog_density;
+	float fog_height;
+	float fog_height_density;
+
+	vec3 fog_light_color;
+	float fog_sun_scatter;
+
+	float fog_aerial_perspective;
+	bool material_uv2_mode;
+
+	float time;
+	float reflection_multiplier; // one normally, zero when rendering reflections
+
+	bool pancake_shadows;
+	uint pad1;
+	uint pad2;
+	uint pad3;
+}
+scene_data;
+
+#ifdef USE_RADIANCE_CUBEMAP_ARRAY
+
+layout(set = 1, binding = 2) uniform textureCubeArray radiance_cubemap;
+
+#else
+
+layout(set = 1, binding = 2) uniform textureCube radiance_cubemap;
+
+#endif
+
+layout(set = 1, binding = 3) uniform textureCubeArray reflection_atlas;
+
+layout(set = 1, binding = 4) uniform texture2D shadow_atlas;
+
+layout(set = 1, binding = 5) uniform texture2D directional_shadow_atlas;
+
+// this needs to change to providing just the lightmap we're using..
+layout(set = 1, binding = 6) uniform texture2DArray lightmap_textures[MAX_LIGHTMAP_TEXTURES];
+
+layout(set = 1, binding = 9) uniform texture2D depth_buffer;
+layout(set = 1, binding = 10) uniform texture2D color_buffer;
+
+/* Set 2 Skeleton & Instancing (can change per item) */
+
+layout(set = 2, binding = 0, std430) restrict readonly buffer Transforms {
+	vec4 data[];
+}
+transforms;
+
+/* Set 3 User Material */

+ 1 - 0
servers/rendering/renderer_rd/shaders/volumetric_fog.glsl

@@ -26,6 +26,7 @@ layout(local_size_x = 4, local_size_y = 4, local_size_z = 4) in;
 #endif
 
 #include "cluster_data_inc.glsl"
+#include "light_data_inc.glsl"
 
 #define M_PI 3.14159265359
 

+ 11 - 10
servers/rendering/renderer_scene_cull.cpp

@@ -2453,18 +2453,19 @@ void RendererSceneCull::_frustum_cull(CullData &cull_data, FrustumCullResult &cu
 				}
 
 				if (geometry_instance_pair_mask & (1 << RS::INSTANCE_DECAL) && (idata.flags & InstanceData::FLAG_GEOM_DECAL_DIRTY)) {
-					//InstanceGeometryData *geom = static_cast<InstanceGeometryData *>(idata.instance->base_data);
-					//todo for GLES3
-					idata.flags &= ~uint32_t(InstanceData::FLAG_GEOM_DECAL_DIRTY);
-					/*for (Set<Instance *>::Element *E = geom->dec.front(); E; E = E->next()) {
-					InstanceReflectionProbeData *reflection_probe = static_cast<InstanceReflectionProbeData *>(E->get()->base_data);
+					InstanceGeometryData *geom = static_cast<InstanceGeometryData *>(idata.instance->base_data);
+					uint32_t idx = 0;
 
-					instance_pair_buffer[idx++] = reflection_probe->instance;
-					if (idx==MAX_INSTANCE_PAIRS) {
-						break;
+					for (Set<Instance *>::Element *E = geom->decals.front(); E; E = E->next()) {
+						InstanceDecalData *decal = static_cast<InstanceDecalData *>(E->get()->base_data);
+
+						instance_pair_buffer[idx++] = decal->instance;
+						if (idx == MAX_INSTANCE_PAIRS) {
+							break;
+						}
 					}
-				}*/
-					//scene_render->geometry_instance_pair_decal_instances(geom->geometry_instance, light_instances, idx);
+					scene_render->geometry_instance_pair_decal_instances(geom->geometry_instance, instance_pair_buffer, idx);
+					idata.flags &= ~uint32_t(InstanceData::FLAG_GEOM_DECAL_DIRTY);
 				}
 
 				if (idata.flags & InstanceData::FLAG_GEOM_GI_PROBE_DIRTY) {

+ 6 - 2
servers/rendering_server.cpp

@@ -2297,8 +2297,12 @@ RenderingServer::RenderingServer() {
 
 	GLOBAL_DEF("rendering/2d/shadow_atlas/size", 2048);
 
-	GLOBAL_DEF("rendering/driver/rd_renderer/use_low_end_renderer", false);
-	GLOBAL_DEF("rendering/driver/rd_renderer/use_low_end_renderer.mobile", true);
+	GLOBAL_DEF_RST("rendering/vulkan/rendering/back_end", 0);
+	GLOBAL_DEF_RST("rendering/vulkan/rendering/back_end.mobile", 1);
+	ProjectSettings::get_singleton()->set_custom_property_info("rendering/vulkan/rendering/back_end",
+			PropertyInfo(Variant::INT,
+					"rendering/vulkan/rendering/back_end",
+					PROPERTY_HINT_ENUM, "ForwardClustered,ForwardMobile"));
 
 	GLOBAL_DEF("rendering/reflections/sky_reflections/roughness_layers", 8);
 	GLOBAL_DEF("rendering/reflections/sky_reflections/texture_array_reflections", true);

Some files were not shown because too many files changed in this diff