Browse Source

Implement 3D textures as import and resource format.

reduz 5 years ago
parent
commit
a674da4eec

+ 76 - 0
core/image.cpp

@@ -363,6 +363,82 @@ void Image::get_mipmap_offset_size_and_dimensions(int p_mipmap, int &r_ofs, int
 	r_size = ofs2 - ofs;
 }
 
+Image::Image3DValidateError Image::validate_3d_image(Image::Format p_format, int p_width, int p_height, int p_depth, bool p_mipmaps, const Vector<Ref<Image>> &p_images) {
+	int w = p_width;
+	int h = p_height;
+	int d = p_depth;
+
+	int arr_ofs = 0;
+
+	while (true) {
+		for (int i = 0; i < d; i++) {
+			int idx = i + arr_ofs;
+			if (idx >= p_images.size()) {
+				return VALIDATE_3D_ERR_MISSING_IMAGES;
+			}
+			if (p_images[idx].is_null() || p_images[idx]->empty()) {
+				return VALIDATE_3D_ERR_IMAGE_EMPTY;
+			}
+			if (p_images[idx]->get_format() != p_format) {
+				return VALIDATE_3D_ERR_IMAGE_FORMAT_MISMATCH;
+			}
+			if (p_images[idx]->get_width() != w || p_images[idx]->get_height() != h) {
+				return VALIDATE_3D_ERR_IMAGE_SIZE_MISMATCH;
+			}
+			if (p_images[idx]->has_mipmaps()) {
+				return VALIDATE_3D_ERR_IMAGE_HAS_MIPMAPS;
+			}
+		}
+
+		arr_ofs += d;
+
+		if (!p_mipmaps) {
+			break;
+		}
+
+		if (w == 1 && h == 1 && d == 1) {
+			break;
+		}
+
+		w = MAX(1, w >> 1);
+		h = MAX(1, h >> 1);
+		d = MAX(1, d >> 1);
+	}
+
+	if (arr_ofs != p_images.size()) {
+		return VALIDATE_3D_ERR_EXTRA_IMAGES;
+	}
+
+	return VALIDATE_3D_OK;
+}
+
+String Image::get_3d_image_validation_error_text(Image3DValidateError p_error) {
+	switch (p_error) {
+		case VALIDATE_3D_OK: {
+			return TTR("Ok");
+		} break;
+		case VALIDATE_3D_ERR_IMAGE_EMPTY: {
+			return TTR("Empty Image found");
+		} break;
+		case VALIDATE_3D_ERR_MISSING_IMAGES: {
+			return TTR("Missing Images");
+		} break;
+		case VALIDATE_3D_ERR_EXTRA_IMAGES: {
+			return TTR("Too many Images");
+		} break;
+		case VALIDATE_3D_ERR_IMAGE_SIZE_MISMATCH: {
+			return TTR("Image size mismatch");
+		} break;
+		case VALIDATE_3D_ERR_IMAGE_FORMAT_MISMATCH: {
+			return TTR("Image format mismatch");
+		} break;
+		case VALIDATE_3D_ERR_IMAGE_HAS_MIPMAPS: {
+			return TTR("Image has included mipmaps");
+		} break;
+	}
+	return String();
+}
+
 int Image::get_width() const {
 	return width;
 }

+ 13 - 0
core/image.h

@@ -230,6 +230,19 @@ public:
 	void get_mipmap_offset_and_size(int p_mipmap, int &r_ofs, int &r_size) const; //get where the mipmap begins in data
 	void get_mipmap_offset_size_and_dimensions(int p_mipmap, int &r_ofs, int &r_size, int &w, int &h) const; //get where the mipmap begins in data
 
+	enum Image3DValidateError {
+		VALIDATE_3D_OK,
+		VALIDATE_3D_ERR_IMAGE_EMPTY,
+		VALIDATE_3D_ERR_MISSING_IMAGES,
+		VALIDATE_3D_ERR_EXTRA_IMAGES,
+		VALIDATE_3D_ERR_IMAGE_SIZE_MISMATCH,
+		VALIDATE_3D_ERR_IMAGE_FORMAT_MISMATCH,
+		VALIDATE_3D_ERR_IMAGE_HAS_MIPMAPS,
+	};
+
+	static Image3DValidateError validate_3d_image(Format p_format, int p_width, int p_height, int p_depth, bool p_mipmaps, const Vector<Ref<Image>> &p_images);
+	static String get_3d_image_validation_error_text(Image3DValidateError p_error);
+
 	/**
 	 * Resize the image, using the preferred interpolation method.
 	 */

+ 4 - 2
editor/editor_node.cpp

@@ -157,6 +157,7 @@
 #include "editor/plugins/sprite_frames_editor_plugin.h"
 #include "editor/plugins/style_box_editor_plugin.h"
 #include "editor/plugins/text_editor.h"
+#include "editor/plugins/texture_3d_editor_plugin.h"
 #include "editor/plugins/texture_editor_plugin.h"
 #include "editor/plugins/texture_layered_editor_plugin.h"
 #include "editor/plugins/texture_region_editor_plugin.h"
@@ -5620,10 +5621,10 @@ EditorNode::EditorNode() {
 		import_cubemap_array->set_mode(ResourceImporterLayeredTexture::MODE_CUBEMAP_ARRAY);
 		ResourceFormatImporter::get_singleton()->add_importer(import_cubemap_array);
 
-		/*Ref<ResourceImporterLayeredTexture> import_3d;
+		Ref<ResourceImporterLayeredTexture> import_3d;
 		import_3d.instance();
 		import_3d->set_mode(ResourceImporterLayeredTexture::MODE_3D);
-		ResourceFormatImporter::get_singleton()->add_importer(import_3d);*/
+		ResourceFormatImporter::get_singleton()->add_importer(import_3d);
 
 		Ref<ResourceImporterImage> import_image;
 		import_image.instance();
@@ -6622,6 +6623,7 @@ EditorNode::EditorNode() {
 	add_editor_plugin(memnew(CurveEditorPlugin(this)));
 	add_editor_plugin(memnew(TextureEditorPlugin(this)));
 	add_editor_plugin(memnew(TextureLayeredEditorPlugin(this)));
+	add_editor_plugin(memnew(Texture3DEditorPlugin(this)));
 	add_editor_plugin(memnew(AudioStreamEditorPlugin(this)));
 	add_editor_plugin(memnew(AudioBusesEditorPlugin(audio_bus_editor)));
 	add_editor_plugin(memnew(Skeleton3DEditorPlugin(this)));

+ 102 - 11
editor/import/resource_importer_layered_texture.cpp

@@ -70,7 +70,7 @@ String ResourceImporterLayeredTexture::get_visible_name() const {
 			return "CubemapArray";
 		} break;
 		case MODE_3D: {
-			return "3D";
+			return "Texture3D";
 		} break;
 	}
 
@@ -156,15 +156,103 @@ void ResourceImporterLayeredTexture::get_import_options(List<ImportOption> *r_op
 }
 
 void ResourceImporterLayeredTexture::_save_tex(Vector<Ref<Image>> p_images, const String &p_to_path, int p_compress_mode, float p_lossy, Image::CompressMode p_vram_compression, Image::CompressSource p_csource, Image::UsedChannels used_channels, bool p_mipmaps, bool p_force_po2) {
-	for (int i = 0; i < p_images.size(); i++) {
-		if (p_force_po2) {
-			p_images.write[i]->resize_to_po2();
+	Vector<Ref<Image>> mipmap_images; //for 3D
+
+	if (mode == MODE_3D) {
+		//3D saves in its own way
+
+		for (int i = 0; i < p_images.size(); i++) {
+			if (p_images.write[i]->has_mipmaps()) {
+				p_images.write[i]->clear_mipmaps();
+			}
+
+			if (p_force_po2) {
+				p_images.write[i]->resize_to_po2();
+			}
 		}
 
 		if (p_mipmaps) {
-			p_images.write[i]->generate_mipmaps();
-		} else {
-			p_images.write[i]->clear_mipmaps();
+			Vector<Ref<Image>> parent_images = p_images;
+			//create 3D mipmaps, this is horrible, though not used very often
+			int w = p_images[0]->get_width();
+			int h = p_images[0]->get_height();
+			int d = p_images.size();
+
+			while (w > 1 || h > 1 || d > 1) {
+				Vector<Ref<Image>> mipmaps;
+				int mm_w = MAX(1, w >> 1);
+				int mm_h = MAX(1, h >> 1);
+				int mm_d = MAX(1, d >> 1);
+
+				for (int i = 0; i < mm_d; i++) {
+					Ref<Image> mm;
+					mm.instance();
+					mm->create(mm_w, mm_h, false, p_images[0]->get_format());
+					Vector3 pos;
+					pos.z = float(i) * float(d) / float(mm_d) + 0.5;
+					for (int x = 0; x < mm_w; x++) {
+						for (int y = 0; y < mm_h; y++) {
+							pos.x = float(x) * float(w) / float(mm_w) + 0.5;
+							pos.y = float(y) * float(h) / float(mm_h) + 0.5;
+
+							Vector3i posi = Vector3i(pos);
+							Vector3 fract = pos - Vector3(posi);
+							Vector3i posi_n = posi;
+							if (posi_n.x < w - 1) {
+								posi_n.x++;
+							}
+							if (posi_n.y < h - 1) {
+								posi_n.y++;
+							}
+							if (posi_n.z < d - 1) {
+								posi_n.z++;
+							}
+
+							Color c000 = parent_images[posi.z]->get_pixel(posi.x, posi.y);
+							Color c100 = parent_images[posi.z]->get_pixel(posi_n.x, posi.y);
+							Color c010 = parent_images[posi.z]->get_pixel(posi.x, posi_n.y);
+							Color c110 = parent_images[posi.z]->get_pixel(posi_n.x, posi_n.y);
+							Color c001 = parent_images[posi_n.z]->get_pixel(posi.x, posi.y);
+							Color c101 = parent_images[posi_n.z]->get_pixel(posi_n.x, posi.y);
+							Color c011 = parent_images[posi_n.z]->get_pixel(posi.x, posi_n.y);
+							Color c111 = parent_images[posi_n.z]->get_pixel(posi_n.x, posi_n.y);
+
+							Color cx00 = c000.lerp(c100, fract.x);
+							Color cx01 = c001.lerp(c101, fract.x);
+							Color cx10 = c010.lerp(c110, fract.x);
+							Color cx11 = c011.lerp(c111, fract.x);
+
+							Color cy0 = cx00.lerp(cx10, fract.y);
+							Color cy1 = cx01.lerp(cx11, fract.y);
+
+							Color cz = cy0.lerp(cy1, fract.z);
+
+							mm->set_pixel(x, y, cz);
+						}
+					}
+
+					mipmaps.push_back(mm);
+				}
+
+				w = mm_w;
+				h = mm_h;
+				d = mm_d;
+
+				mipmap_images.append_array(mipmaps);
+				parent_images = mipmaps;
+			}
+		}
+	} else {
+		for (int i = 0; i < p_images.size(); i++) {
+			if (p_force_po2) {
+				p_images.write[i]->resize_to_po2();
+			}
+
+			if (p_mipmaps) {
+				p_images.write[i]->generate_mipmaps();
+			} else {
+				p_images.write[i]->clear_mipmaps();
+			}
 		}
 	}
 
@@ -175,13 +263,12 @@ void ResourceImporterLayeredTexture::_save_tex(Vector<Ref<Image>> p_images, cons
 	f->store_8('L');
 
 	f->store_32(StreamTextureLayered::FORMAT_VERSION);
-	f->store_32(p_images.size());
+	f->store_32(p_images.size()); //2d layers or 3d depth
 	f->store_32(mode);
-	f->store_32(0); //dataformat
-	f->store_32(0); //mipmap limit
+	f->store_32(0);
 
-	//reserved
 	f->store_32(0);
+	f->store_32(mipmap_images.size()); // amount of mipmaps
 	f->store_32(0);
 	f->store_32(0);
 
@@ -189,6 +276,10 @@ void ResourceImporterLayeredTexture::_save_tex(Vector<Ref<Image>> p_images, cons
 		ResourceImporterTexture::save_to_stex_format(f, p_images[i], ResourceImporterTexture::CompressMode(p_compress_mode), used_channels, p_vram_compression, p_lossy);
 	}
 
+	for (int i = 0; i < mipmap_images.size(); i++) {
+		ResourceImporterTexture::save_to_stex_format(f, mipmap_images[i], ResourceImporterTexture::CompressMode(p_compress_mode), used_channels, p_vram_compression, p_lossy);
+	}
+
 	f->close();
 }
 

+ 0 - 4
editor/import/resource_importer_layered_texture.h

@@ -93,10 +93,6 @@ private:
 	static const char *compression_formats[];
 
 protected:
-	static void _texture_reimport_srgb(const Ref<StreamTexture2D> &p_tex);
-	static void _texture_reimport_3d(const Ref<StreamTexture2D> &p_tex);
-	static void _texture_reimport_normal(const Ref<StreamTexture2D> &p_tex);
-
 	static ResourceImporterLayeredTexture *singleton;
 
 public:

+ 213 - 0
editor/plugins/texture_3d_editor_plugin.cpp

@@ -0,0 +1,213 @@
+/*************************************************************************/
+/*  texture_3d_editor_plugin.cpp                                         */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2020 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 "texture_3d_editor_plugin.h"
+
+#include "core/io/resource_loader.h"
+#include "core/project_settings.h"
+#include "editor/editor_settings.h"
+
+void Texture3DEditor::_gui_input(Ref<InputEvent> p_event) {
+}
+
+void Texture3DEditor::_texture_rect_draw() {
+	texture_rect->draw_rect(Rect2(Point2(), texture_rect->get_size()), Color(1, 1, 1, 1));
+}
+
+void Texture3DEditor::_notification(int p_what) {
+	if (p_what == NOTIFICATION_READY) {
+		//get_scene()->connect("node_removed",this,"_node_removed");
+	}
+	if (p_what == NOTIFICATION_RESIZED) {
+		_texture_rect_update_area();
+	}
+
+	if (p_what == NOTIFICATION_DRAW) {
+		Ref<Texture2D> checkerboard = get_theme_icon("Checkerboard", "EditorIcons");
+		Size2 size = get_size();
+
+		draw_texture_rect(checkerboard, Rect2(Point2(), size), true);
+	}
+}
+
+void Texture3DEditor::_changed_callback(Object *p_changed, const char *p_prop) {
+	if (!is_visible()) {
+		return;
+	}
+	update();
+}
+
+void Texture3DEditor::_update_material() {
+	material->set_shader_param("layer", (layer->get_value() + 0.5) / texture->get_depth());
+	material->set_shader_param("tex", texture->get_rid());
+
+	String format = Image::get_format_name(texture->get_format());
+
+	String text;
+	text = itos(texture->get_width()) + "x" + itos(texture->get_height()) + "x" + itos(texture->get_depth()) + " " + format;
+
+	info->set_text(text);
+}
+
+void Texture3DEditor::_make_shaders() {
+	String shader_3d = ""
+					   "shader_type canvas_item;\n"
+					   "uniform sampler3D tex;\n"
+					   "uniform float layer;\n"
+					   "void fragment() {\n"
+					   "  COLOR = textureLod(tex,vec3(UV,layer),0.0);\n"
+					   "}";
+
+	shader.instance();
+	shader->set_code(shader_3d);
+	material.instance();
+	material->set_shader(shader);
+}
+
+void Texture3DEditor::_texture_rect_update_area() {
+	Size2 size = get_size();
+	int tex_width = texture->get_width() * size.height / texture->get_height();
+	int tex_height = size.height;
+
+	if (tex_width > size.width) {
+		tex_width = size.width;
+		tex_height = texture->get_height() * tex_width / texture->get_width();
+	}
+
+	// Prevent the texture from being unpreviewable after the rescale, so that we can still see something
+	if (tex_height <= 0) {
+		tex_height = 1;
+	}
+	if (tex_width <= 0) {
+		tex_width = 1;
+	}
+
+	int ofs_x = (size.width - tex_width) / 2;
+	int ofs_y = (size.height - tex_height) / 2;
+
+	texture_rect->set_position(Vector2(ofs_x, ofs_y));
+	texture_rect->set_size(Vector2(tex_width, tex_height));
+}
+
+void Texture3DEditor::edit(Ref<Texture3D> p_texture) {
+	if (!texture.is_null()) {
+		texture->remove_change_receptor(this);
+	}
+
+	texture = p_texture;
+
+	if (!texture.is_null()) {
+		if (shader.is_null()) {
+			_make_shaders();
+		}
+
+		texture->add_change_receptor(this);
+		update();
+		texture_rect->set_material(material);
+		setting = true;
+		layer->set_max(texture->get_depth() - 1);
+		layer->set_value(0);
+		layer->show();
+		_update_material();
+		setting = false;
+		_texture_rect_update_area();
+	} else {
+		hide();
+	}
+}
+
+void Texture3DEditor::_bind_methods() {
+	ClassDB::bind_method(D_METHOD("_gui_input"), &Texture3DEditor::_gui_input);
+	ClassDB::bind_method(D_METHOD("_layer_changed"), &Texture3DEditor::_layer_changed);
+}
+
+Texture3DEditor::Texture3DEditor() {
+	set_texture_repeat(TextureRepeat::TEXTURE_REPEAT_ENABLED);
+	set_custom_minimum_size(Size2(1, 150));
+	texture_rect = memnew(Control);
+	texture_rect->connect("draw", callable_mp(this, &Texture3DEditor::_texture_rect_draw));
+	texture_rect->set_mouse_filter(MOUSE_FILTER_IGNORE);
+	add_child(texture_rect);
+
+	layer = memnew(SpinBox);
+	layer->set_step(1);
+	layer->set_max(100);
+	add_child(layer);
+	layer->set_anchor(MARGIN_RIGHT, 1);
+	layer->set_anchor(MARGIN_LEFT, 1);
+	layer->set_h_grow_direction(GROW_DIRECTION_BEGIN);
+	layer->set_modulate(Color(1, 1, 1, 0.8));
+	info = memnew(Label);
+	add_child(info);
+	info->set_anchor(MARGIN_RIGHT, 1);
+	info->set_anchor(MARGIN_LEFT, 1);
+	info->set_anchor(MARGIN_BOTTOM, 1);
+	info->set_anchor(MARGIN_TOP, 1);
+	info->set_h_grow_direction(GROW_DIRECTION_BEGIN);
+	info->set_v_grow_direction(GROW_DIRECTION_BEGIN);
+	info->add_theme_color_override("font_color", Color(1, 1, 1, 1));
+	info->add_theme_color_override("font_color_shadow", Color(0, 0, 0, 0.5));
+	info->add_theme_color_override("font_color_shadow", Color(0, 0, 0, 0.5));
+	info->add_theme_constant_override("shadow_as_outline", 1);
+	info->add_theme_constant_override("shadow_offset_x", 2);
+	info->add_theme_constant_override("shadow_offset_y", 2);
+
+	setting = false;
+	layer->connect("value_changed", Callable(this, "_layer_changed"));
+}
+
+Texture3DEditor::~Texture3DEditor() {
+	if (!texture.is_null()) {
+		texture->remove_change_receptor(this);
+	}
+}
+
+//
+bool EditorInspectorPlugin3DTexture::can_handle(Object *p_object) {
+	return Object::cast_to<Texture3D>(p_object) != nullptr;
+}
+
+void EditorInspectorPlugin3DTexture::parse_begin(Object *p_object) {
+	Texture3D *texture = Object::cast_to<Texture3D>(p_object);
+	if (!texture) {
+		return;
+	}
+	Ref<Texture3D> m(texture);
+
+	Texture3DEditor *editor = memnew(Texture3DEditor);
+	editor->edit(m);
+	add_custom_control(editor);
+}
+
+Texture3DEditorPlugin::Texture3DEditorPlugin(EditorNode *p_node) {
+	Ref<EditorInspectorPlugin3DTexture> plugin;
+	plugin.instance();
+	add_inspector_plugin(plugin);
+}

+ 93 - 0
editor/plugins/texture_3d_editor_plugin.h

@@ -0,0 +1,93 @@
+/*************************************************************************/
+/*  texture_3d_editor_plugin.h                                           */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2020 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 TEXTURE_3D_EDITOR_PLUGIN_H
+#define TEXTURE_3D_EDITOR_PLUGIN_H
+
+#include "editor/editor_node.h"
+#include "editor/editor_plugin.h"
+#include "scene/resources/shader.h"
+#include "scene/resources/texture.h"
+
+class Texture3DEditor : public Control {
+	GDCLASS(Texture3DEditor, Control);
+
+	SpinBox *layer;
+	Label *info;
+	Ref<Texture3D> texture;
+
+	Ref<Shader> shader;
+	Ref<ShaderMaterial> material;
+
+	Control *texture_rect;
+
+	void _make_shaders();
+
+	void _update_material();
+	bool setting;
+	void _layer_changed(double) {
+		if (!setting) {
+			_update_material();
+		}
+	}
+
+	void _texture_rect_update_area();
+	void _texture_rect_draw();
+
+protected:
+	void _notification(int p_what);
+	void _gui_input(Ref<InputEvent> p_event);
+	void _changed_callback(Object *p_changed, const char *p_prop) override;
+	static void _bind_methods();
+
+public:
+	void edit(Ref<Texture3D> p_texture);
+	Texture3DEditor();
+	~Texture3DEditor();
+};
+
+class EditorInspectorPlugin3DTexture : public EditorInspectorPlugin {
+	GDCLASS(EditorInspectorPlugin3DTexture, EditorInspectorPlugin);
+
+public:
+	virtual bool can_handle(Object *p_object) override;
+	virtual void parse_begin(Object *p_object) override;
+};
+
+class Texture3DEditorPlugin : public EditorPlugin {
+	GDCLASS(Texture3DEditorPlugin, EditorPlugin);
+
+public:
+	virtual String get_name() const override { return "Texture3D"; }
+
+	Texture3DEditorPlugin(EditorNode *p_node);
+};
+
+#endif // TEXTURE_EDITOR_PLUGIN_H

+ 10 - 0
scene/register_scene_types.cpp

@@ -230,6 +230,7 @@ static Ref<ResourceFormatLoaderDynamicFont> resource_loader_dynamic_font;
 
 static Ref<ResourceFormatLoaderStreamTexture2D> resource_loader_stream_texture;
 static Ref<ResourceFormatLoaderStreamTextureLayered> resource_loader_texture_layered;
+static Ref<ResourceFormatLoaderStreamTexture3D> resource_loader_texture_3d;
 
 static Ref<ResourceFormatLoaderBMFont> resource_loader_bmfont;
 
@@ -252,6 +253,9 @@ void register_scene_types() {
 	resource_loader_texture_layered.instance();
 	ResourceLoader::add_resource_format_loader(resource_loader_texture_layered);
 
+	resource_loader_texture_3d.instance();
+	ResourceLoader::add_resource_format_loader(resource_loader_texture_3d);
+
 	resource_saver_text.instance();
 	ResourceSaver::add_resource_format_saver(resource_saver_text, true);
 
@@ -701,6 +705,9 @@ void register_scene_types() {
 	ClassDB::register_class<CameraTexture>();
 	ClassDB::register_virtual_class<TextureLayered>();
 	ClassDB::register_virtual_class<ImageTextureLayered>();
+	ClassDB::register_virtual_class<Texture3D>();
+	ClassDB::register_class<ImageTexture3D>();
+	ClassDB::register_class<StreamTexture3D>();
 	ClassDB::register_class<Cubemap>();
 	ClassDB::register_class<CubemapArray>();
 	ClassDB::register_class<Texture2DArray>();
@@ -946,6 +953,9 @@ void unregister_scene_types() {
 	ResourceLoader::remove_resource_format_loader(resource_loader_texture_layered);
 	resource_loader_texture_layered.unref();
 
+	ResourceLoader::remove_resource_format_loader(resource_loader_texture_3d);
+	resource_loader_texture_3d.unref();
+
 	ResourceLoader::remove_resource_format_loader(resource_loader_stream_texture);
 	resource_loader_stream_texture.unref();
 

+ 304 - 0
scene/resources/texture.cpp

@@ -791,8 +791,312 @@ String ResourceFormatLoaderStreamTexture2D::get_resource_type(const String &p_pa
 	return "";
 }
 
+////////////////////////////////////
+
+TypedArray<Image> Texture3D::_get_data() const {
+	Vector<Ref<Image>> data = get_data();
+
+	TypedArray<Image> ret;
+	ret.resize(data.size());
+	for (int i = 0; i < data.size(); i++) {
+		ret[i] = data[i];
+	}
+	return ret;
+}
+
+void Texture3D::_bind_methods() {
+	ClassDB::bind_method(D_METHOD("get_format"), &Texture3D::get_format);
+	ClassDB::bind_method(D_METHOD("get_width"), &Texture3D::get_width);
+	ClassDB::bind_method(D_METHOD("get_height"), &Texture3D::get_height);
+	ClassDB::bind_method(D_METHOD("get_depth"), &Texture3D::get_depth);
+	ClassDB::bind_method(D_METHOD("has_mipmaps"), &Texture3D::has_mipmaps);
+	ClassDB::bind_method(D_METHOD("get_data"), &Texture3D::_get_data);
+}
 //////////////////////////////////////////
 
+Image::Format ImageTexture3D::get_format() const {
+	return format;
+}
+int ImageTexture3D::get_width() const {
+	return width;
+}
+int ImageTexture3D::get_height() const {
+	return height;
+}
+int ImageTexture3D::get_depth() const {
+	return depth;
+}
+bool ImageTexture3D::has_mipmaps() const {
+	return mipmaps;
+}
+
+Error ImageTexture3D::_create(Image::Format p_format, int p_width, int p_height, int p_depth, bool p_mipmaps, const TypedArray<Image> &p_data) {
+	Vector<Ref<Image>> images;
+	images.resize(p_data.size());
+	for (int i = 0; i < images.size(); i++) {
+		images.write[i] = p_data[i];
+	}
+	return create(p_format, p_width, p_height, p_depth, p_mipmaps, images);
+}
+
+void ImageTexture3D::_update(const TypedArray<Image> &p_data) {
+	Vector<Ref<Image>> images;
+	images.resize(p_data.size());
+	for (int i = 0; i < images.size(); i++) {
+		images.write[i] = p_data[i];
+	}
+	return update(images);
+}
+
+Error ImageTexture3D::create(Image::Format p_format, int p_width, int p_height, int p_depth, bool p_mipmaps, const Vector<Ref<Image>> &p_data) {
+	RID tex = RenderingServer::get_singleton()->texture_3d_create(p_format, p_width, p_height, p_depth, p_mipmaps, p_data);
+	ERR_FAIL_COND_V(tex.is_null(), ERR_CANT_CREATE);
+
+	if (texture.is_valid()) {
+		RenderingServer::get_singleton()->texture_replace(texture, tex);
+	}
+
+	return OK;
+}
+
+void ImageTexture3D::update(const Vector<Ref<Image>> &p_data) {
+	ERR_FAIL_COND(!texture.is_valid());
+	RenderingServer::get_singleton()->texture_3d_update(texture, p_data);
+}
+
+Vector<Ref<Image>> ImageTexture3D::get_data() const {
+	ERR_FAIL_COND_V(!texture.is_valid(), Vector<Ref<Image>>());
+	return RS::get_singleton()->texture_3d_get(texture);
+}
+
+RID ImageTexture3D::get_rid() const {
+	if (!texture.is_valid()) {
+		texture = RS::get_singleton()->texture_3d_placeholder_create();
+	}
+	return texture;
+}
+void ImageTexture3D::set_path(const String &p_path, bool p_take_over) {
+	if (texture.is_valid()) {
+		RenderingServer::get_singleton()->texture_set_path(texture, p_path);
+	}
+
+	Resource::set_path(p_path, p_take_over);
+}
+
+void ImageTexture3D::_bind_methods() {
+	ClassDB::bind_method(D_METHOD("create", "format", "width", "height", "depth", "use_mipmaps", "data"), &ImageTexture3D::_create);
+	ClassDB::bind_method(D_METHOD("update", "data"), &ImageTexture3D::_update);
+}
+
+ImageTexture3D::ImageTexture3D() {
+}
+
+ImageTexture3D::~ImageTexture3D() {
+	if (texture.is_valid()) {
+		RS::get_singleton()->free(texture);
+	}
+}
+
+////////////////////////////////////////////
+
+void StreamTexture3D::set_path(const String &p_path, bool p_take_over) {
+	if (texture.is_valid()) {
+		RenderingServer::get_singleton()->texture_set_path(texture, p_path);
+	}
+
+	Resource::set_path(p_path, p_take_over);
+}
+
+Image::Format StreamTexture3D::get_format() const {
+	return format;
+}
+
+Error StreamTexture3D::_load_data(const String &p_path, Vector<Ref<Image>> &r_data, Image::Format &r_format, int &r_width, int &r_height, int &r_depth, bool &r_mipmaps) {
+	FileAccessRef f = FileAccess::open(p_path, FileAccess::READ);
+	ERR_FAIL_COND_V(!f, ERR_CANT_OPEN);
+
+	uint8_t header[4];
+	f->get_buffer(header, 4);
+	ERR_FAIL_COND_V(header[0] != 'G' || header[1] != 'S' || header[2] != 'T' || header[3] != 'L', ERR_FILE_UNRECOGNIZED);
+
+	//stored as stream textures (used for lossless and lossy compression)
+	uint32_t version = f->get_32();
+
+	if (version > FORMAT_VERSION) {
+		ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, "Stream texture file is too new.");
+	}
+
+	r_depth = f->get_32(); //depth
+	f->get_32(); //ignored (mode)
+	f->get_32(); // ignored (data format)
+
+	f->get_32(); //ignored
+	int mipmaps = f->get_32();
+	f->get_32(); //ignored
+	f->get_32(); //ignored
+
+	r_mipmaps = mipmaps != 0;
+
+	r_data.clear();
+
+	for (int i = 0; i < (r_depth + mipmaps); i++) {
+		Ref<Image> image = StreamTexture2D::load_image_from_file(f, 0);
+		ERR_FAIL_COND_V(image.is_null() || image->empty(), ERR_CANT_OPEN);
+		if (i == 0) {
+			r_format = image->get_format();
+			r_width = image->get_width();
+			r_height = image->get_height();
+		}
+		r_data.push_back(image);
+	}
+
+	return OK;
+}
+
+Error StreamTexture3D::load(const String &p_path) {
+	Vector<Ref<Image>> data;
+
+	int tw, th, td;
+	Image::Format tfmt;
+	bool tmm;
+
+	Error err = _load_data(p_path, data, tfmt, tw, th, td, tmm);
+	if (err) {
+		return err;
+	}
+
+	if (texture.is_valid()) {
+		RID new_texture = RS::get_singleton()->texture_3d_create(tfmt, tw, th, td, tmm, data);
+		RS::get_singleton()->texture_replace(texture, new_texture);
+	} else {
+		texture = RS::get_singleton()->texture_3d_create(tfmt, tw, th, td, tmm, data);
+	}
+
+	w = tw;
+	h = th;
+	d = td;
+	mipmaps = tmm;
+	format = tfmt;
+
+	path_to_file = p_path;
+
+	if (get_path() == String()) {
+		//temporarily set path if no path set for resource, helps find errors
+		RenderingServer::get_singleton()->texture_set_path(texture, p_path);
+	}
+
+	_change_notify();
+	emit_changed();
+	return OK;
+}
+
+String StreamTexture3D::get_load_path() const {
+	return path_to_file;
+}
+
+int StreamTexture3D::get_width() const {
+	return w;
+}
+
+int StreamTexture3D::get_height() const {
+	return h;
+}
+
+int StreamTexture3D::get_depth() const {
+	return d;
+}
+
+bool StreamTexture3D::has_mipmaps() const {
+	return mipmaps;
+}
+
+RID StreamTexture3D::get_rid() const {
+	if (!texture.is_valid()) {
+		texture = RS::get_singleton()->texture_3d_placeholder_create();
+	}
+	return texture;
+}
+
+Vector<Ref<Image>> StreamTexture3D::get_data() const {
+	if (texture.is_valid()) {
+		return RS::get_singleton()->texture_3d_get(texture);
+	} else {
+		return Vector<Ref<Image>>();
+	}
+}
+
+void StreamTexture3D::reload_from_file() {
+	String path = get_path();
+	if (!path.is_resource_file()) {
+		return;
+	}
+
+	path = ResourceLoader::path_remap(path); //remap for translation
+	path = ResourceLoader::import_remap(path); //remap for import
+	if (!path.is_resource_file()) {
+		return;
+	}
+
+	load(path);
+}
+
+void StreamTexture3D::_validate_property(PropertyInfo &property) const {
+}
+
+void StreamTexture3D::_bind_methods() {
+	ClassDB::bind_method(D_METHOD("load", "path"), &StreamTexture3D::load);
+	ClassDB::bind_method(D_METHOD("get_load_path"), &StreamTexture3D::get_load_path);
+
+	ADD_PROPERTY(PropertyInfo(Variant::STRING, "load_path", PROPERTY_HINT_FILE, "*.stex"), "load", "get_load_path");
+}
+
+StreamTexture3D::StreamTexture3D() {
+	format = Image::FORMAT_MAX;
+	w = 0;
+	h = 0;
+	d = 0;
+	mipmaps = false;
+}
+
+StreamTexture3D::~StreamTexture3D() {
+	if (texture.is_valid()) {
+		RS::get_singleton()->free(texture);
+	}
+}
+
+/////////////////////////////
+
+RES ResourceFormatLoaderStreamTexture3D::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, bool p_no_cache) {
+	Ref<StreamTexture3D> st;
+	st.instance();
+	Error err = st->load(p_path);
+	if (r_error) {
+		*r_error = err;
+	}
+	if (err != OK) {
+		return RES();
+	}
+
+	return st;
+}
+
+void ResourceFormatLoaderStreamTexture3D::get_recognized_extensions(List<String> *p_extensions) const {
+	p_extensions->push_back("stex3d");
+}
+
+bool ResourceFormatLoaderStreamTexture3D::handles_type(const String &p_type) const {
+	return p_type == "StreamTexture3D";
+}
+
+String ResourceFormatLoaderStreamTexture3D::get_resource_type(const String &p_path) const {
+	if (p_path.get_extension().to_lower() == "stex3d") {
+		return "StreamTexture3D";
+	}
+	return "";
+}
+
+////////////////////////////////////////////
+
 int AtlasTexture::get_width() const {
 	if (region.size.width == 0) {
 		if (atlas.is_valid()) {

+ 116 - 0
scene/resources/texture.h

@@ -515,6 +515,122 @@ public:
 	virtual String get_resource_type(const String &p_path) const;
 };
 
+class Texture3D : public Texture {
+	GDCLASS(Texture3D, Texture);
+
+protected:
+	static void _bind_methods();
+
+	TypedArray<Image> _get_data() const;
+
+public:
+	virtual Image::Format get_format() const = 0;
+	virtual int get_width() const = 0;
+	virtual int get_height() const = 0;
+	virtual int get_depth() const = 0;
+	virtual bool has_mipmaps() const = 0;
+	virtual Vector<Ref<Image>> get_data() const = 0;
+};
+
+class ImageTexture3D : public Texture3D {
+	GDCLASS(ImageTexture3D, Texture3D);
+
+	mutable RID texture;
+
+	Image::Format format = Image::FORMAT_MAX;
+	int width = 1;
+	int height = 1;
+	int depth = 1;
+	bool mipmaps = false;
+
+protected:
+	static void _bind_methods();
+
+	Error _create(Image::Format p_format, int p_width, int p_height, int p_depth, bool p_mipmaps, const TypedArray<Image> &p_data);
+	void _update(const TypedArray<Image> &p_data);
+
+public:
+	virtual Image::Format get_format() const override;
+	virtual int get_width() const override;
+	virtual int get_height() const override;
+	virtual int get_depth() const override;
+	virtual bool has_mipmaps() const override;
+
+	Error create(Image::Format p_format, int p_width, int p_height, int p_depth, bool p_mipmaps, const Vector<Ref<Image>> &p_data);
+	void update(const Vector<Ref<Image>> &p_data);
+	virtual Vector<Ref<Image>> get_data() const override;
+
+	virtual RID get_rid() const override;
+	virtual void set_path(const String &p_path, bool p_take_over = false) override;
+
+	ImageTexture3D();
+	~ImageTexture3D();
+};
+
+class StreamTexture3D : public Texture3D {
+	GDCLASS(StreamTexture3D, Texture3D);
+
+public:
+	enum DataFormat {
+		DATA_FORMAT_IMAGE,
+		DATA_FORMAT_LOSSLESS,
+		DATA_FORMAT_LOSSY,
+		DATA_FORMAT_BASIS_UNIVERSAL,
+	};
+
+	enum {
+		FORMAT_VERSION = 1
+	};
+
+	enum FormatBits {
+		FORMAT_MASK_IMAGE_FORMAT = (1 << 20) - 1,
+		FORMAT_BIT_LOSSLESS = 1 << 20,
+		FORMAT_BIT_LOSSY = 1 << 21,
+		FORMAT_BIT_STREAM = 1 << 22,
+		FORMAT_BIT_HAS_MIPMAPS = 1 << 23,
+	};
+
+private:
+	Error _load_data(const String &p_path, Vector<Ref<Image>> &r_data, Image::Format &r_format, int &r_width, int &r_height, int &r_depth, bool &r_mipmaps);
+	String path_to_file;
+	mutable RID texture;
+	Image::Format format;
+	int w, h, d;
+	bool mipmaps;
+
+	virtual void reload_from_file() override;
+
+protected:
+	static void _bind_methods();
+	void _validate_property(PropertyInfo &property) const override;
+
+public:
+	Image::Format get_format() const override;
+	Error load(const String &p_path);
+	String get_load_path() const;
+
+	int get_width() const override;
+	int get_height() const override;
+	int get_depth() const override;
+	virtual bool has_mipmaps() const override;
+	virtual RID get_rid() const override;
+
+	virtual void set_path(const String &p_path, bool p_take_over) override;
+
+	virtual Vector<Ref<Image>> get_data() const override;
+
+	StreamTexture3D();
+	~StreamTexture3D();
+};
+
+class ResourceFormatLoaderStreamTexture3D : public ResourceFormatLoader {
+public:
+	virtual RES load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, bool p_no_cache = false);
+	virtual void get_recognized_extensions(List<String> *p_extensions) const;
+	virtual bool handles_type(const String &p_type) const;
+	virtual String get_resource_type(const String &p_path) const;
+};
+
 class CurveTexture : public Texture2D {
 	GDCLASS(CurveTexture, Texture2D);
 	RES_BASE_EXTENSION("curvetex")

+ 3 - 3
servers/rendering/rasterizer.h

@@ -331,12 +331,12 @@ public:
 
 	virtual RID texture_2d_create(const Ref<Image> &p_image) = 0;
 	virtual RID texture_2d_layered_create(const Vector<Ref<Image>> &p_layers, RS::TextureLayeredType p_layered_type) = 0;
-	virtual RID texture_3d_create(const Vector<Ref<Image>> &p_slices) = 0; //all slices, then all the mipmaps, must be coherent
+	virtual RID texture_3d_create(Image::Format, int p_width, int p_height, int p_depth, bool p_mipmaps, const Vector<Ref<Image>> &p_data) = 0;
 	virtual RID texture_proxy_create(RID p_base) = 0; //all slices, then all the mipmaps, must be coherent
 
 	virtual void texture_2d_update_immediate(RID p_texture, const Ref<Image> &p_image, int p_layer = 0) = 0; //mostly used for video and streaming
 	virtual void texture_2d_update(RID p_texture, const Ref<Image> &p_image, int p_layer = 0) = 0;
-	virtual void texture_3d_update(RID p_texture, const Ref<Image> &p_image, int p_depth, int p_mipmap) = 0;
+	virtual void texture_3d_update(RID p_texture, const Vector<Ref<Image>> &p_data) = 0;
 	virtual void texture_proxy_update(RID p_proxy, RID p_base) = 0;
 
 	//these two APIs can be used together or in combination with the others.
@@ -346,7 +346,7 @@ public:
 
 	virtual Ref<Image> texture_2d_get(RID p_texture) const = 0;
 	virtual Ref<Image> texture_2d_layer_get(RID p_texture, int p_layer) const = 0;
-	virtual Ref<Image> texture_3d_slice_get(RID p_texture, int p_depth, int p_mipmap) const = 0;
+	virtual Vector<Ref<Image>> texture_3d_get(RID p_texture) const = 0;
 
 	virtual void texture_replace(RID p_texture, RID p_by_texture) = 0;
 	virtual void texture_set_size_override(RID p_texture, int p_width, int p_height) = 0;

+ 211 - 7
servers/rendering/rasterizer_rd/rasterizer_storage_rd.cpp

@@ -715,8 +715,120 @@ RID RasterizerStorageRD::texture_2d_layered_create(const Vector<Ref<Image>> &p_l
 	return texture_owner.make_rid(texture);
 }
 
-RID RasterizerStorageRD::texture_3d_create(const Vector<Ref<Image>> &p_slices) {
-	return RID();
+RID RasterizerStorageRD::texture_3d_create(Image::Format p_format, int p_width, int p_height, int p_depth, bool p_mipmaps, const Vector<Ref<Image>> &p_data) {
+	ERR_FAIL_COND_V(p_data.size() == 0, RID());
+	Image::Image3DValidateError verr = Image::validate_3d_image(p_format, p_width, p_height, p_depth, p_mipmaps, p_data);
+	if (verr != Image::VALIDATE_3D_OK) {
+		ERR_FAIL_V_MSG(RID(), Image::get_3d_image_validation_error_text(verr));
+	}
+
+	TextureToRDFormat ret_format;
+	Image::Format validated_format = Image::FORMAT_MAX;
+	Vector<uint8_t> all_data;
+	uint32_t mipmap_count = 0;
+	Vector<Texture::BufferSlice3D> slices;
+	{
+		Vector<Ref<Image>> images;
+		uint32_t all_data_size = 0;
+		images.resize(p_data.size());
+		for (int i = 0; i < p_data.size(); i++) {
+			TextureToRDFormat f;
+			images.write[i] = _validate_texture_format(p_data[i], f);
+			if (i == 0) {
+				ret_format = f;
+				validated_format = images[0]->get_format();
+			}
+
+			all_data_size += images[i]->get_data().size();
+		}
+
+		all_data.resize(all_data_size); //consolidate all data here
+		uint32_t offset = 0;
+		Size2i prev_size;
+		for (int i = 0; i < p_data.size(); i++) {
+			uint32_t s = images[i]->get_data().size();
+
+			copymem(&all_data.write[offset], images[i]->get_data().ptr(), s);
+			{
+				Texture::BufferSlice3D slice;
+				slice.size.width = images[i]->get_width();
+				slice.size.height = images[i]->get_height();
+				slice.offset = offset;
+				slice.buffer_size = s;
+				slices.push_back(slice);
+			}
+			offset += s;
+
+			Size2i img_size(images[i]->get_width(), images[i]->get_height());
+			if (img_size != prev_size) {
+				mipmap_count++;
+			}
+			prev_size = img_size;
+		}
+	}
+
+	Texture texture;
+
+	texture.type = Texture::TYPE_3D;
+	texture.width = p_width;
+	texture.height = p_height;
+	texture.depth = p_depth;
+	texture.mipmaps = mipmap_count;
+	texture.format = p_data[0]->get_format();
+	texture.validated_format = validated_format;
+
+	texture.buffer_size_3d = all_data.size();
+	texture.buffer_slices_3d = slices;
+
+	texture.rd_type = RD::TEXTURE_TYPE_3D;
+	texture.rd_format = ret_format.format;
+	texture.rd_format_srgb = ret_format.format_srgb;
+
+	RD::TextureFormat rd_format;
+	RD::TextureView rd_view;
+	{ //attempt register
+		rd_format.format = texture.rd_format;
+		rd_format.width = texture.width;
+		rd_format.height = texture.height;
+		rd_format.depth = texture.depth;
+		rd_format.array_layers = 1;
+		rd_format.mipmaps = texture.mipmaps;
+		rd_format.type = texture.rd_type;
+		rd_format.samples = RD::TEXTURE_SAMPLES_1;
+		rd_format.usage_bits = RD::TEXTURE_USAGE_SAMPLING_BIT | RD::TEXTURE_USAGE_CAN_UPDATE_BIT | RD::TEXTURE_USAGE_CAN_COPY_FROM_BIT;
+		if (texture.rd_format_srgb != RD::DATA_FORMAT_MAX) {
+			rd_format.shareable_formats.push_back(texture.rd_format);
+			rd_format.shareable_formats.push_back(texture.rd_format_srgb);
+		}
+	}
+	{
+		rd_view.swizzle_r = ret_format.swizzle_r;
+		rd_view.swizzle_g = ret_format.swizzle_g;
+		rd_view.swizzle_b = ret_format.swizzle_b;
+		rd_view.swizzle_a = ret_format.swizzle_a;
+	}
+	Vector<Vector<uint8_t>> data_slices;
+	data_slices.push_back(all_data); //one slice
+
+	texture.rd_texture = RD::get_singleton()->texture_create(rd_format, rd_view, data_slices);
+	ERR_FAIL_COND_V(texture.rd_texture.is_null(), RID());
+	if (texture.rd_format_srgb != RD::DATA_FORMAT_MAX) {
+		rd_view.format_override = texture.rd_format_srgb;
+		texture.rd_texture_srgb = RD::get_singleton()->texture_create_shared(rd_view, texture.rd_texture);
+		if (texture.rd_texture_srgb.is_null()) {
+			RD::get_singleton()->free(texture.rd_texture);
+			ERR_FAIL_COND_V(texture.rd_texture_srgb.is_null(), RID());
+		}
+	}
+
+	//used for 2D, overridable
+	texture.width_2d = texture.width;
+	texture.height_2d = texture.height;
+	texture.is_render_target = false;
+	texture.rd_view = rd_view;
+	texture.is_proxy = false;
+
+	return texture_owner.make_rid(texture);
 }
 
 RID RasterizerStorageRD::texture_proxy_create(RID p_base) {
@@ -772,7 +884,41 @@ void RasterizerStorageRD::texture_2d_update(RID p_texture, const Ref<Image> &p_i
 	_texture_2d_update(p_texture, p_image, p_layer, false);
 }
 
-void RasterizerStorageRD::texture_3d_update(RID p_texture, const Ref<Image> &p_image, int p_depth, int p_mipmap) {
+void RasterizerStorageRD::texture_3d_update(RID p_texture, const Vector<Ref<Image>> &p_data) {
+	Texture *tex = texture_owner.getornull(p_texture);
+	ERR_FAIL_COND(!tex);
+	ERR_FAIL_COND(tex->type != Texture::TYPE_3D);
+	Image::Image3DValidateError verr = Image::validate_3d_image(tex->format, tex->width, tex->height, tex->depth, tex->mipmaps > 1, p_data);
+	if (verr != Image::VALIDATE_3D_OK) {
+		ERR_FAIL_MSG(Image::get_3d_image_validation_error_text(verr));
+	}
+
+	Vector<uint8_t> all_data;
+	{
+		Vector<Ref<Image>> images;
+		uint32_t all_data_size = 0;
+		images.resize(p_data.size());
+		for (int i = 0; i < p_data.size(); i++) {
+			Ref<Image> image = p_data[i];
+			if (image->get_format() != tex->validated_format) {
+				image = image->duplicate();
+				image->convert(tex->validated_format);
+			}
+			all_data_size += images[i]->get_data().size();
+			images.push_back(image);
+		}
+
+		all_data.resize(all_data_size); //consolidate all data here
+		uint32_t offset = 0;
+
+		for (int i = 0; i < p_data.size(); i++) {
+			uint32_t s = images[i]->get_data().size();
+			copymem(&all_data.write[offset], images[i]->get_data().ptr(), s);
+			offset += s;
+		}
+	}
+
+	RD::get_singleton()->texture_update(tex->rd_texture, 0, all_data, true);
 }
 
 void RasterizerStorageRD::texture_proxy_update(RID p_texture, RID p_proxy_to) {
@@ -858,7 +1004,25 @@ RID RasterizerStorageRD::texture_2d_layered_placeholder_create(RS::TextureLayere
 }
 
 RID RasterizerStorageRD::texture_3d_placeholder_create() {
-	return RID();
+	//this could be better optimized to reuse an existing image , done this way
+	//for now to get it working
+	Ref<Image> image;
+	image.instance();
+	image->create(4, 4, false, Image::FORMAT_RGBA8);
+
+	for (int i = 0; i < 4; i++) {
+		for (int j = 0; j < 4; j++) {
+			image->set_pixel(i, j, Color(1, 0, 1, 1));
+		}
+	}
+
+	Vector<Ref<Image>> images;
+	//cube
+	for (int i = 0; i < 4; i++) {
+		images.push_back(image);
+	}
+
+	return texture_3d_create(Image::FORMAT_RGBA8, 4, 4, 4, false, images);
 }
 
 Ref<Image> RasterizerStorageRD::texture_2d_get(RID p_texture) const {
@@ -890,11 +1054,51 @@ Ref<Image> RasterizerStorageRD::texture_2d_get(RID p_texture) const {
 }
 
 Ref<Image> RasterizerStorageRD::texture_2d_layer_get(RID p_texture, int p_layer) const {
-	return Ref<Image>();
+	Texture *tex = texture_owner.getornull(p_texture);
+	ERR_FAIL_COND_V(!tex, Ref<Image>());
+
+	Vector<uint8_t> data = RD::get_singleton()->texture_get_data(tex->rd_texture, p_layer);
+	ERR_FAIL_COND_V(data.size() == 0, Ref<Image>());
+	Ref<Image> image;
+	image.instance();
+	image->create(tex->width, tex->height, tex->mipmaps > 1, tex->validated_format, data);
+	ERR_FAIL_COND_V(image->empty(), Ref<Image>());
+	if (tex->format != tex->validated_format) {
+		image->convert(tex->format);
+	}
+
+	return image;
 }
 
-Ref<Image> RasterizerStorageRD::texture_3d_slice_get(RID p_texture, int p_depth, int p_mipmap) const {
-	return Ref<Image>();
+Vector<Ref<Image>> RasterizerStorageRD::texture_3d_get(RID p_texture) const {
+	Texture *tex = texture_owner.getornull(p_texture);
+	ERR_FAIL_COND_V(!tex, Vector<Ref<Image>>());
+	ERR_FAIL_COND_V(tex->type != Texture::TYPE_3D, Vector<Ref<Image>>());
+
+	Vector<uint8_t> all_data = RD::get_singleton()->texture_get_data(tex->rd_texture, 0);
+
+	ERR_FAIL_COND_V(all_data.size() != (int)tex->buffer_size_3d, Vector<Ref<Image>>());
+
+	Vector<Ref<Image>> ret;
+
+	for (int i = 0; i < tex->buffer_slices_3d.size(); i++) {
+		const Texture::BufferSlice3D &bs = tex->buffer_slices_3d[i];
+		ERR_FAIL_COND_V(bs.offset >= (uint32_t)all_data.size(), Vector<Ref<Image>>());
+		ERR_FAIL_COND_V(bs.offset + bs.buffer_size > (uint32_t)all_data.size(), Vector<Ref<Image>>());
+		Vector<uint8_t> sub_region = all_data.subarray(bs.offset, bs.offset + bs.buffer_size - 1);
+
+		Ref<Image> img;
+		img.instance();
+		img->create(bs.size.width, bs.size.height, false, tex->validated_format, sub_region);
+		ERR_FAIL_COND_V(img->empty(), Vector<Ref<Image>>());
+		if (tex->format != tex->validated_format) {
+			img->convert(tex->format);
+		}
+
+		ret.push_back(img);
+	}
+
+	return ret;
 }
 
 void RasterizerStorageRD::texture_replace(RID p_texture, RID p_by_texture) {

+ 11 - 3
servers/rendering/rasterizer_rd/rasterizer_storage_rd.h

@@ -205,6 +205,14 @@ private:
 		int height_2d;
 		int width_2d;
 
+		struct BufferSlice3D {
+			Size2i size;
+			uint32_t offset = 0;
+			uint32_t buffer_size = 0;
+		};
+		Vector<BufferSlice3D> buffer_slices_3d;
+		uint32_t buffer_size_3d = 0;
+
 		bool is_render_target;
 		bool is_proxy;
 
@@ -980,14 +988,14 @@ public:
 
 	virtual RID texture_2d_create(const Ref<Image> &p_image);
 	virtual RID texture_2d_layered_create(const Vector<Ref<Image>> &p_layers, RS::TextureLayeredType p_layered_type);
-	virtual RID texture_3d_create(const Vector<Ref<Image>> &p_slices); //all slices, then all the mipmaps, must be coherent
+	virtual RID texture_3d_create(Image::Format p_format, int p_width, int p_height, int p_depth, bool p_mipmaps, const Vector<Ref<Image>> &p_data); //all slices, then all the mipmaps, must be coherent
 	virtual RID texture_proxy_create(RID p_base);
 
 	virtual void _texture_2d_update(RID p_texture, const Ref<Image> &p_image, int p_layer, bool p_immediate);
 
 	virtual void texture_2d_update_immediate(RID p_texture, const Ref<Image> &p_image, int p_layer = 0); //mostly used for video and streaming
 	virtual void texture_2d_update(RID p_texture, const Ref<Image> &p_image, int p_layer = 0);
-	virtual void texture_3d_update(RID p_texture, const Ref<Image> &p_image, int p_depth, int p_mipmap);
+	virtual void texture_3d_update(RID p_texture, const Vector<Ref<Image>> &p_data);
 	virtual void texture_proxy_update(RID p_texture, RID p_proxy_to);
 
 	//these two APIs can be used together or in combination with the others.
@@ -997,7 +1005,7 @@ public:
 
 	virtual Ref<Image> texture_2d_get(RID p_texture) const;
 	virtual Ref<Image> texture_2d_layer_get(RID p_texture, int p_layer) const;
-	virtual Ref<Image> texture_3d_slice_get(RID p_texture, int p_depth, int p_mipmap) const;
+	virtual Vector<Ref<Image>> texture_3d_get(RID p_texture) const;
 
 	virtual void texture_replace(RID p_texture, RID p_by_texture);
 	virtual void texture_set_size_override(RID p_texture, int p_width, int p_height);

+ 11 - 3
servers/rendering/rendering_server_raster.h

@@ -114,6 +114,14 @@ public:
 	m_r m_name(m_type1 arg1, m_type2 arg2, m_type3 arg3, m_type4 arg4) { return BINDBASE->m_name(arg1, arg2, arg3, arg4); }
 #define BIND4RC(m_r, m_name, m_type1, m_type2, m_type3, m_type4) \
 	m_r m_name(m_type1 arg1, m_type2 arg2, m_type3 arg3, m_type4 arg4) const { return BINDBASE->m_name(arg1, arg2, arg3, arg4); }
+#define BIND5R(m_r, m_name, m_type1, m_type2, m_type3, m_type4, m_type5) \
+	m_r m_name(m_type1 arg1, m_type2 arg2, m_type3 arg3, m_type4 arg4, m_type5 arg5) { return BINDBASE->m_name(arg1, arg2, arg3, arg4, arg5); }
+#define BIND5RC(m_r, m_name, m_type1, m_type2, m_type3, m_type4, m_type5) \
+	m_r m_name(m_type1 arg1, m_type2 arg2, m_type3 arg3, m_type4 arg4, m_type5 arg5) const { return BINDBASE->m_name(arg1, arg2, arg3, arg4, arg5); }
+#define BIND6R(m_r, m_name, m_type1, m_type2, m_type3, m_type4, m_type5, m_type6) \
+	m_r m_name(m_type1 arg1, m_type2 arg2, m_type3 arg3, m_type4 arg4, m_type5 arg5, m_type6 arg6) { return BINDBASE->m_name(arg1, arg2, arg3, arg4, arg5, arg6); }
+#define BIND6RC(m_r, m_name, m_type1, m_type2, m_type3, m_type4, m_type5, m_type6) \
+	m_r m_name(m_type1 arg1, m_type2 arg2, m_type3 arg3, m_type4 arg4, m_type5 arg5, m_type6 arg6) const { return BINDBASE->m_name(arg1, arg2, arg3, arg4, arg5, arg6); }
 
 #define BIND0(m_name) \
 	void m_name() { DISPLAY_CHANGED BINDBASE->m_name(); }
@@ -160,14 +168,14 @@ public:
 	//these go pass-through, as they can be called from any thread
 	BIND1R(RID, texture_2d_create, const Ref<Image> &)
 	BIND2R(RID, texture_2d_layered_create, const Vector<Ref<Image>> &, TextureLayeredType)
-	BIND1R(RID, texture_3d_create, const Vector<Ref<Image>> &)
+	BIND6R(RID, texture_3d_create, Image::Format, int, int, int, bool, const Vector<Ref<Image>> &)
 	BIND1R(RID, texture_proxy_create, RID)
 
 	//goes pass-through
 	BIND3(texture_2d_update_immediate, RID, const Ref<Image> &, int)
 	//these go through command queue if they are in another thread
 	BIND3(texture_2d_update, RID, const Ref<Image> &, int)
-	BIND4(texture_3d_update, RID, const Ref<Image> &, int, int)
+	BIND2(texture_3d_update, RID, const Vector<Ref<Image>> &)
 	BIND2(texture_proxy_update, RID, RID)
 
 	//these also go pass-through
@@ -177,7 +185,7 @@ public:
 
 	BIND1RC(Ref<Image>, texture_2d_get, RID)
 	BIND2RC(Ref<Image>, texture_2d_layer_get, RID, int)
-	BIND3RC(Ref<Image>, texture_3d_slice_get, RID, int, int)
+	BIND1RC(Vector<Ref<Image>>, texture_3d_get, RID)
 
 	BIND2(texture_replace, RID, RID)
 

+ 3 - 3
servers/rendering/rendering_server_wrap_mt.h

@@ -79,14 +79,14 @@ public:
 	//these go pass-through, as they can be called from any thread
 	virtual RID texture_2d_create(const Ref<Image> &p_image) { return rendering_server->texture_2d_create(p_image); }
 	virtual RID texture_2d_layered_create(const Vector<Ref<Image>> &p_layers, TextureLayeredType p_layered_type) { return rendering_server->texture_2d_layered_create(p_layers, p_layered_type); }
-	virtual RID texture_3d_create(const Vector<Ref<Image>> &p_slices) { return rendering_server->texture_3d_create(p_slices); }
+	virtual RID texture_3d_create(Image::Format p_format, int p_width, int p_height, int p_depth, bool p_mipmaps, const Vector<Ref<Image>> &p_data) { return rendering_server->texture_3d_create(p_format, p_width, p_height, p_depth, p_mipmaps, p_data); }
 	virtual RID texture_proxy_create(RID p_base) { return rendering_server->texture_proxy_create(p_base); }
 
 	//goes pass-through
 	virtual void texture_2d_update_immediate(RID p_texture, const Ref<Image> &p_image, int p_layer = 0) { rendering_server->texture_2d_update_immediate(p_texture, p_image, p_layer); }
 	//these go through command queue if they are in another thread
 	FUNC3(texture_2d_update, RID, const Ref<Image> &, int)
-	FUNC4(texture_3d_update, RID, const Ref<Image> &, int, int)
+	FUNC2(texture_3d_update, RID, const Vector<Ref<Image>> &)
 	FUNC2(texture_proxy_update, RID, RID)
 
 	//these also go pass-through
@@ -96,7 +96,7 @@ public:
 
 	FUNC1RC(Ref<Image>, texture_2d_get, RID)
 	FUNC2RC(Ref<Image>, texture_2d_layer_get, RID, int)
-	FUNC3RC(Ref<Image>, texture_3d_slice_get, RID, int, int)
+	FUNC1RC(Vector<Ref<Image>>, texture_3d_get, RID)
 
 	FUNC2(texture_replace, RID, RID)
 

+ 3 - 3
servers/rendering_server.h

@@ -97,12 +97,12 @@ public:
 
 	virtual RID texture_2d_create(const Ref<Image> &p_image) = 0;
 	virtual RID texture_2d_layered_create(const Vector<Ref<Image>> &p_layers, TextureLayeredType p_layered_type) = 0;
-	virtual RID texture_3d_create(const Vector<Ref<Image>> &p_slices) = 0; //all slices, then all the mipmaps, must be coherent
+	virtual RID texture_3d_create(Image::Format, int p_width, int p_height, int p_depth, bool p_mipmaps, const Vector<Ref<Image>> &p_data) = 0; //all slices, then all the mipmaps, must be coherent
 	virtual RID texture_proxy_create(RID p_base) = 0;
 
 	virtual void texture_2d_update_immediate(RID p_texture, const Ref<Image> &p_image, int p_layer = 0) = 0; //mostly used for video and streaming
 	virtual void texture_2d_update(RID p_texture, const Ref<Image> &p_image, int p_layer = 0) = 0;
-	virtual void texture_3d_update(RID p_texture, const Ref<Image> &p_image, int p_depth, int p_mipmap) = 0;
+	virtual void texture_3d_update(RID p_texture, const Vector<Ref<Image>> &p_data) = 0;
 	virtual void texture_proxy_update(RID p_texture, RID p_proxy_to) = 0;
 
 	//these two APIs can be used together or in combination with the others.
@@ -112,7 +112,7 @@ public:
 
 	virtual Ref<Image> texture_2d_get(RID p_texture) const = 0;
 	virtual Ref<Image> texture_2d_layer_get(RID p_texture, int p_layer) const = 0;
-	virtual Ref<Image> texture_3d_slice_get(RID p_texture, int p_depth, int p_mipmap) const = 0;
+	virtual Vector<Ref<Image>> texture_3d_get(RID p_texture) const = 0;
 
 	virtual void texture_replace(RID p_texture, RID p_by_texture) = 0;
 	virtual void texture_set_size_override(RID p_texture, int p_width, int p_height) = 0;