ソースを参照

Added proper import support for 3D and Array textures

Juan Linietsky 7 年 前
コミット
edde52c8de

+ 18 - 5
core/image.cpp

@@ -987,8 +987,10 @@ int Image::_get_dst_image_size(int p_width, int p_height, Format p_format, int &
 	int pixsize = get_format_pixel_size(p_format);
 	int pixshift = get_format_pixel_rshift(p_format);
 	int block = get_format_block_size(p_format);
-	int minw, minh;
-	get_format_min_pixel_size(p_format, minw, minh);
+	//technically, you can still compress up to 1 px no matter the format, so commenting this
+	//int minw, minh;
+	//get_format_min_pixel_size(p_format, minw, minh);
+	int minw = 1, minh = 1;
 
 	while (true) {
 
@@ -1304,7 +1306,7 @@ void Image::create(int p_width, int p_height, bool p_use_mipmaps, Format p_forma
 	int size = _get_dst_image_size(p_width, p_height, p_format, mm, p_use_mipmaps ? -1 : 0);
 
 	if (size != p_data.size()) {
-		ERR_EXPLAIN("Expected data size of " + itos(size) + " in Image::create()");
+		ERR_EXPLAIN("Expected data size of " + itos(size) + " bytes in Image::create(), got instead " + itos(p_data.size()) + " bytes.");
 		ERR_FAIL_COND(p_data.size() != size);
 	}
 
@@ -1592,10 +1594,10 @@ Error Image::save_png(const String &p_path) const {
 	return save_png_func(p_path, Ref<Image>((Image *)this));
 }
 
-int Image::get_image_data_size(int p_width, int p_height, Format p_format, int p_mipmaps) {
+int Image::get_image_data_size(int p_width, int p_height, Format p_format, bool p_mipmaps) {
 
 	int mm;
-	return _get_dst_image_size(p_width, p_height, p_format, mm, p_mipmaps);
+	return _get_dst_image_size(p_width, p_height, p_format, mm, p_mipmaps ? -1 : 0);
 }
 
 int Image::get_image_required_mipmaps(int p_width, int p_height, Format p_format) {
@@ -2376,6 +2378,17 @@ Image::DetectChannels Image::get_detected_channels() {
 	return DETECTED_RGBA;
 }
 
+void Image::optimize_channels() {
+	switch (get_detected_channels()) {
+		case DETECTED_L: convert(FORMAT_L8); break;
+		case DETECTED_LA: convert(FORMAT_LA8); break;
+		case DETECTED_R: convert(FORMAT_R8); break;
+		case DETECTED_RG: convert(FORMAT_RG8); break;
+		case DETECTED_RGB: convert(FORMAT_RGB8); break;
+		case DETECTED_RGBA: convert(FORMAT_RGBA8); break;
+	}
+}
+
 void Image::_bind_methods() {
 
 	ClassDB::bind_method(D_METHOD("get_width"), &Image::get_width);

+ 4 - 2
core/image.h

@@ -116,7 +116,8 @@ public:
 	enum CompressSource {
 		COMPRESS_SOURCE_GENERIC,
 		COMPRESS_SOURCE_SRGB,
-		COMPRESS_SOURCE_NORMAL
+		COMPRESS_SOURCE_NORMAL,
+		COMPRESS_SOURCE_LAYERED,
 	};
 
 	//some functions provided by something else
@@ -272,7 +273,7 @@ public:
 	static int get_format_block_size(Format p_format);
 	static void get_format_min_pixel_size(Format p_format, int &r_w, int &r_h);
 
-	static int get_image_data_size(int p_width, int p_height, Format p_format, int p_mipmaps = 0);
+	static int get_image_data_size(int p_width, int p_height, Format p_format, bool p_mipmaps = false);
 	static int get_image_required_mipmaps(int p_width, int p_height, Format p_format);
 
 	enum CompressMode {
@@ -329,6 +330,7 @@ public:
 	};
 
 	DetectChannels get_detected_channels();
+	void optimize_channels();
 
 	Color get_pixelv(const Point2 &p_src) const;
 	Color get_pixel(int p_x, int p_y) const;

+ 5 - 1
drivers/gles2/rasterizer_storage_gles2.cpp

@@ -370,13 +370,17 @@ void RasterizerStorageGLES2::texture_allocate(RID p_texture, int p_width, int p_
 	switch (p_type) {
 		case VS::TEXTURE_TYPE_2D: {
 			texture->target = GL_TEXTURE_2D;
+			texture->images.resize(1);
 		} break;
 		case VS::TEXTURE_TYPE_CUBEMAP: {
 			texture->target = GL_TEXTURE_CUBE_MAP;
+			texture->images.resize(6);
 		} break;
 		case VS::TEXTURE_TYPE_2D_ARRAY: {
+			texture->images.resize(p_depth_3d);
 		} break;
 		case VS::TEXTURE_TYPE_3D: {
+			texture->images.resize(p_depth_3d);
 		} break;
 	}
 
@@ -419,7 +423,7 @@ void RasterizerStorageGLES2::texture_set_data(RID p_texture, const Ref<Image> &p
 	bool compressed = false;
 
 	if (config.keep_original_textures && !(texture->flags & VS::TEXTURE_FLAG_USED_FOR_STREAMING)) {
-		texture->images[p_layer] = p_image;
+		texture->images.write[p_layer] = p_image;
 	}
 
 	Ref<Image> img = _get_gl_image_and_format(p_image, p_image->get_format(), texture->flags, format, internal_format, type, compressed);

+ 1 - 1
drivers/gles2/rasterizer_storage_gles2.h

@@ -258,7 +258,7 @@ public:
 
 		RenderTarget *render_target;
 
-		Ref<Image> images[6];
+		Vector<Ref<Image> > images;
 
 		bool redraw_if_visible;
 

+ 5 - 1
drivers/gles3/rasterizer_storage_gles3.cpp

@@ -622,15 +622,19 @@ void RasterizerStorageGLES3::texture_allocate(RID p_texture, int p_width, int p_
 	switch (p_type) {
 		case VS::TEXTURE_TYPE_2D: {
 			texture->target = GL_TEXTURE_2D;
+			texture->images.resize(1);
 		} break;
 		case VS::TEXTURE_TYPE_CUBEMAP: {
 			texture->target = GL_TEXTURE_CUBE_MAP;
+			texture->images.resize(6);
 		} break;
 		case VS::TEXTURE_TYPE_2D_ARRAY: {
 			texture->target = GL_TEXTURE_2D_ARRAY;
+			texture->images.resize(p_depth_3d);
 		} break;
 		case VS::TEXTURE_TYPE_3D: {
 			texture->target = GL_TEXTURE_3D;
+			texture->images.resize(p_depth_3d);
 		} break;
 	}
 
@@ -703,7 +707,7 @@ void RasterizerStorageGLES3::texture_set_data(RID p_texture, const Ref<Image> &p
 	bool srgb;
 
 	if (config.keep_original_textures && !(texture->flags & VS::TEXTURE_FLAG_USED_FOR_STREAMING)) {
-		texture->images[p_layer] = p_image;
+		texture->images.write[p_layer] = p_image;
 	}
 
 	Ref<Image> img = _get_gl_image_and_format(p_image, p_image->get_format(), texture->flags, format, internal_format, type, compressed, srgb);

+ 1 - 1
drivers/gles3/rasterizer_storage_gles3.h

@@ -277,7 +277,7 @@ public:
 
 		RenderTarget *render_target;
 
-		Ref<Image> images[6];
+		Vector<Ref<Image> > images;
 
 		VisualServer::TextureDetectCallback detect_3d;
 		void *detect_3d_ud;

+ 11 - 0
editor/editor_node.cpp

@@ -63,6 +63,7 @@
 #include "editor/import/resource_importer_bitmask.h"
 #include "editor/import/resource_importer_csv_translation.h"
 #include "editor/import/resource_importer_image.h"
+#include "editor/import/resource_importer_layered_texture.h"
 #include "editor/import/resource_importer_obj.h"
 #include "editor/import/resource_importer_scene.h"
 #include "editor/import/resource_importer_texture.h"
@@ -4666,6 +4667,16 @@ EditorNode::EditorNode() {
 		import_texture.instance();
 		ResourceFormatImporter::get_singleton()->add_importer(import_texture);
 
+		Ref<ResourceImporterLayeredTexture> import_3d;
+		import_3d.instance();
+		import_3d->set_3d(true);
+		ResourceFormatImporter::get_singleton()->add_importer(import_3d);
+
+		Ref<ResourceImporterLayeredTexture> import_array;
+		import_array.instance();
+		import_array->set_3d(false);
+		ResourceFormatImporter::get_singleton()->add_importer(import_array);
+
 		Ref<ResourceImporterImage> import_image;
 		import_image.instance();
 		ResourceFormatImporter::get_singleton()->add_importer(import_image);

+ 75 - 0
editor/icons/icon_texture_3_d.svg

@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="16"
+   height="16"
+   version="1.1"
+   viewBox="0 0 16 16"
+   id="svg6"
+   sodipodi:docname="icon_texture_3_d.svg"
+   inkscape:version="0.92.3 (2405546, 2018-03-11)">
+  <metadata
+     id="metadata12">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs10" />
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1853"
+     inkscape:window-height="1016"
+     id="namedview8"
+     showgrid="false"
+     inkscape:zoom="29.5"
+     inkscape:cx="15.226978"
+     inkscape:cy="9.4909723"
+     inkscape:window-x="67"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="svg6" />
+  <g
+     id="g830"
+     transform="translate(0.35954582,-0.28763666)">
+    <path
+       d="M 2,1 C 1.4477153,1 1,1.4477153 1,2 v 12 c 0,0.552285 0.4477153,1 1,1 h 12 c 0.552285,0 1,-0.447715 1,-1 V 2 C 15,1.4477153 14.552285,1 14,1 Z m 1,2 h 10 v 8 H 3 Z"
+       id="path2"
+       inkscape:connector-curvature="0"
+       style="fill:#e0e0e0;fill-opacity:0.99607999"
+       sodipodi:nodetypes="sssssssssccccc" />
+  </g>
+  <g
+     aria-label="3D"
+     transform="scale(0.9167105,1.0908569)"
+     style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:8.12847996px;line-height:1.25;font-family:Ubuntu;-inkscape-font-specification:'Ubuntu Bold';letter-spacing:0px;word-spacing:0px;fill:#e0e0e0;fill-opacity:0.99607843;stroke:none;stroke-width:0.20321201"
+     id="text834">
+    <path
+       d="m 5.8175194,8.9717502 q -0.2194689,0 -0.4633233,-0.032514 Q 5.1103417,8.9148508 4.8827442,8.86608 4.6551468,8.8173091 4.4681918,8.7604097 4.2812367,8.7035104 4.1755665,8.6547395 L 4.4112924,7.646808 q 0.2113405,0.089413 0.5364797,0.1950835 0.3332677,0.097542 0.8209765,0.097542 0.5608651,0 0.8209764,-0.2113404 0.2601114,-0.2113405 0.2601114,-0.5689936 0,-0.219469 -0.097542,-0.3657816 Q 6.6628814,6.6388764 6.5003118,6.5494632 6.3377422,6.4519214 6.1101447,6.4194075 5.8906758,6.3787651 5.6386929,6.3787651 H 5.167241 V 5.4033475 h 0.5364797 q 0.1788266,0 0.3413962,-0.032514 0.1706981,-0.032514 0.3007537,-0.1056703 0.1300557,-0.081285 0.203212,-0.2113404 0.081285,-0.1381842 0.081285,-0.3413962 0,-0.1544411 -0.065028,-0.2682398 Q 6.5003118,4.3303881 6.3946415,4.2572318 6.2970998,4.1840755 6.1589156,4.1515616 6.0288599,4.1109192 5.8906758,4.1109192 q -0.3495247,0 -0.6502784,0.1056702 Q 4.9477721,4.3222597 4.7039177,4.4767008 L 4.2731082,3.5906965 Q 4.4031639,3.5094117 4.573862,3.4199984 4.7526886,3.3305851 4.964029,3.2574288 5.1753695,3.1842725 5.4110954,3.1355016 q 0.2438544,-0.048771 0.5120943,-0.048771 0.4958373,0 0.8534904,0.1219272 0.3657816,0.1137987 0.6015075,0.3332677 0.2357259,0.2113405 0.3495246,0.5039657 0.1137987,0.2844968 0.1137987,0.625893 0,0.3332677 -0.186955,0.6502784 -0.186955,0.3088822 -0.5039657,0.4714518 0.4389379,0.1788266 0.6746638,0.5364797 0.2438544,0.3495246 0.2438544,0.8453619 0,0.3901671 -0.1300557,0.7234347 Q 7.808997,8.22393 7.5326287,8.4677844 7.2562604,8.7035104 6.825451,8.8416945 6.40277,8.9717502 5.8175194,8.9717502 Z"
+       style="fill:#e0e0e0;fill-opacity:0.99607843;stroke-width:0.20321201"
+       id="path836" />
+    <path
+       d="m 10.502445,7.817506 q 0.08941,0.00813 0.203212,0.016257 0.121927,0 0.284497,0 0.951032,0 1.406227,-0.4795803 0.463323,-0.4795803 0.463323,-1.3249422 0,-0.8860044 -0.438938,-1.3411992 -0.438938,-0.4551949 -1.38997,-0.4551949 -0.130055,0 -0.26824,0.00813 -0.138184,0 -0.260111,0.016257 z M 14.16839,6.0292405 q 0,0.7315631 -0.227598,1.2761713 -0.227597,0.5446082 -0.650278,0.9022613 -0.414553,0.3576531 -1.01606,0.5364797 -0.601508,0.1788265 -1.349328,0.1788265 -0.341396,0 -0.796591,-0.032514 Q 9.6733402,8.86608 9.2344022,8.7766667 v -5.486724 q 0.438938,-0.081285 0.9103898,-0.1056702 0.47958,-0.032514 0.820976,-0.032514 0.723435,0 1.308686,0.1625696 0.593379,0.1625696 1.01606,0.5120943 0.422681,0.3495246 0.650278,0.8941328 0.227598,0.5446081 0.227598,1.3086853 z"
+       style="fill:#e0e0e0;fill-opacity:0.99607843;stroke-width:0.20321201"
+       id="path838" />
+  </g>
+</svg>

+ 77 - 0
editor/icons/icon_texture_array.svg

@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="16"
+   height="16"
+   version="1.1"
+   viewBox="0 0 16 16"
+   id="svg6"
+   sodipodi:docname="icon_texture_array.svg"
+   inkscape:version="0.92.3 (2405546, 2018-03-11)">
+  <metadata
+     id="metadata12">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs10" />
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1526"
+     inkscape:window-height="766"
+     id="namedview8"
+     showgrid="false"
+     inkscape:zoom="29.5"
+     inkscape:cx="8.3117238"
+     inkscape:cy="9.4909723"
+     inkscape:window-x="67"
+     inkscape:window-y="27"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="svg6" />
+  <g
+     id="g830"
+     transform="translate(0.35954582,-0.28763666)">
+    <path
+       d="M 2,1 C 1.4477153,1 1,1.4477153 1,2 v 12 c 0,0.552285 0.4477153,1 1,1 h 12 c 0.552285,0 1,-0.447715 1,-1 V 2 C 15,1.4477153 14.552285,1 14,1 Z m 1,2 h 10 v 8 H 3 Z"
+       id="path2"
+       inkscape:connector-curvature="0"
+       style="fill:#e0e0e0;fill-opacity:0.99607999"
+       sodipodi:nodetypes="sssssssssccccc" />
+  </g>
+  <g
+     aria-label="[]"
+     transform="matrix(1.6197742,0,0,0.750929,-3.7231532,1.8329569)"
+     style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:8.29580784px;line-height:1.25;font-family:Ubuntu;-inkscape-font-specification:'Ubuntu Bold';letter-spacing:0px;word-spacing:0px;fill:#e0e0e0;fill-opacity:0.99607843;stroke:none;stroke-width:0.2073952"
+     id="text834">
+    <path
+       d="M 4.7302951,2.4553483 H 6.978459 V 3.4425495 H 5.9082998 V 9.4984892 H 6.978459 V 10.48569 H 4.7302951 Z"
+       style="fill:#e0e0e0;fill-opacity:0.99607843;stroke-width:0.2073952"
+       id="path862"
+       inkscape:connector-curvature="0" />
+    <path
+       d="M 10.138643,10.48569 H 7.8904794 V 9.4984892 H 8.9606386 V 3.4425495 H 7.8904794 V 2.4553483 h 2.2481636 z"
+       style="fill:#e0e0e0;fill-opacity:0.99607843;stroke-width:0.2073952"
+       id="path864"
+       inkscape:connector-curvature="0" />
+  </g>
+</svg>

+ 274 - 0
editor/import/resource_importer_layered_texture.cpp

@@ -0,0 +1,274 @@
+#include "resource_importer_layered_texture.h"
+
+#include "resource_importer_texture.h"
+
+#include "editor/editor_file_system.h"
+#include "editor/editor_node.h"
+#include "io/config_file.h"
+#include "io/image_loader.h"
+#include "scene/resources/texture.h"
+
+String ResourceImporterLayeredTexture::get_importer_name() const {
+
+	return is_3d ? "texture_3d" : "texture_array";
+}
+
+String ResourceImporterLayeredTexture::get_visible_name() const {
+
+	return is_3d ? "Texture3D" : "TextureArray";
+}
+void ResourceImporterLayeredTexture::get_recognized_extensions(List<String> *p_extensions) const {
+
+	ImageLoader::get_recognized_extensions(p_extensions);
+}
+String ResourceImporterLayeredTexture::get_save_extension() const {
+	return is_3d ? "tex3d" : "texarr";
+}
+
+String ResourceImporterLayeredTexture::get_resource_type() const {
+
+	return is_3d ? "Texture3D" : "TextureArray";
+}
+
+bool ResourceImporterLayeredTexture::get_option_visibility(const String &p_option, const Map<StringName, Variant> &p_options) const {
+
+	return true;
+}
+
+int ResourceImporterLayeredTexture::get_preset_count() const {
+	return 3;
+}
+String ResourceImporterLayeredTexture::get_preset_name(int p_idx) const {
+
+	static const char *preset_names[] = {
+		"3D",
+		"2D",
+		"ColorCorrect"
+	};
+
+	return preset_names[p_idx];
+}
+
+void ResourceImporterLayeredTexture::get_import_options(List<ImportOption> *r_options, int p_preset) const {
+
+	r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "compress/mode", PROPERTY_HINT_ENUM, "Lossless,Video RAM,Uncompressed", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), p_preset == PRESET_3D ? 1 : 0));
+	r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "flags/repeat", PROPERTY_HINT_ENUM, "Disabled,Enabled,Mirrored"), 0));
+	r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "flags/filter"), true));
+	r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "flags/mipmaps"), p_preset == PRESET_COLOR_CORRECT ? 0 : 1));
+	r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "flags/srgb", PROPERTY_HINT_ENUM, "Disable,Enable"), p_preset == PRESET_3D ? 1 : 0));
+	r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "slices/horizontal", PROPERTY_HINT_RANGE, "1,256,1"), p_preset == PRESET_COLOR_CORRECT ? 16 : 8));
+	r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "slices/vertical", PROPERTY_HINT_RANGE, "1,256,1"), p_preset == PRESET_COLOR_CORRECT ? 1 : 8));
+}
+
+void ResourceImporterLayeredTexture::_save_tex(const Vector<Ref<Image> > &p_images, const String &p_to_path, int p_compress_mode, Image::CompressMode p_vram_compression, bool p_mipmaps, int p_texture_flags) {
+
+	FileAccess *f = FileAccess::open(p_to_path, FileAccess::WRITE);
+	f->store_8('G');
+	f->store_8('D');
+	if (is_3d) {
+		f->store_8('3');
+	} else {
+		f->store_8('A');
+	}
+	f->store_8('T'); //godot streamable texture
+
+	f->store_32(p_images[0]->get_width());
+	f->store_32(p_images[0]->get_height());
+	f->store_32(p_images.size()); //depth
+	f->store_32(p_texture_flags);
+	if (p_compress_mode != COMPRESS_VIDEO_RAM) {
+		//vram needs to do a first compression to tell what the format is, for the rest its ok
+		f->store_32(p_images[0]->get_format());
+		f->store_32(p_compress_mode); // 0 - lossless (PNG), 1 - vram, 2 - uncompressed
+	}
+
+	if ((p_compress_mode == COMPRESS_LOSSLESS) && p_images[0]->get_format() > Image::FORMAT_RGBA8) {
+		p_compress_mode = COMPRESS_UNCOMPRESSED; //these can't go as lossy
+	}
+
+	for (int i = 0; i < p_images.size(); i++) {
+
+		switch (p_compress_mode) {
+			case COMPRESS_LOSSLESS: {
+
+				Ref<Image> image = p_images[i]->duplicate();
+				if (p_mipmaps) {
+					image->generate_mipmaps();
+				} else {
+					image->clear_mipmaps();
+				}
+
+				int mmc = image->get_mipmap_count() + 1;
+				f->store_32(mmc);
+
+				for (int i = 0; i < mmc; i++) {
+
+					if (i > 0) {
+						image->shrink_x2();
+					}
+
+					PoolVector<uint8_t> data = Image::lossless_packer(image);
+					int data_len = data.size();
+					f->store_32(data_len);
+
+					PoolVector<uint8_t>::Read r = data.read();
+					f->store_buffer(r.ptr(), data_len);
+				}
+
+			} break;
+			case COMPRESS_VIDEO_RAM: {
+
+				Ref<Image> image = p_images[i]->duplicate();
+				image->generate_mipmaps(false);
+
+				Image::CompressSource csource = Image::COMPRESS_SOURCE_LAYERED;
+				image->compress(p_vram_compression, csource, 0.7);
+
+				if (i == 0) {
+					//hack so we can properly tell the format
+					f->store_32(image->get_format());
+					f->store_32(p_compress_mode); // 0 - lossless (PNG), 1 - vram, 2 - uncompressed
+				}
+
+				PoolVector<uint8_t> data = image->get_data();
+				int dl = data.size();
+
+				PoolVector<uint8_t>::Read r = data.read();
+				f->store_buffer(r.ptr(), dl);
+			} break;
+			case COMPRESS_UNCOMPRESSED: {
+
+				Ref<Image> image = p_images[i]->duplicate();
+
+				if (p_mipmaps) {
+					image->generate_mipmaps();
+				} else {
+					image->clear_mipmaps();
+				}
+
+				PoolVector<uint8_t> data = image->get_data();
+				int dl = data.size();
+
+				PoolVector<uint8_t>::Read r = data.read();
+
+				f->store_buffer(r.ptr(), dl);
+
+			} break;
+		}
+	}
+
+	memdelete(f);
+}
+
+Error ResourceImporterLayeredTexture::import(const String &p_source_file, const String &p_save_path, const Map<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files) {
+
+	int compress_mode = p_options["compress/mode"];
+	int repeat = p_options["flags/repeat"];
+	bool filter = p_options["flags/filter"];
+	bool mipmaps = p_options["flags/mipmaps"];
+	int srgb = p_options["flags/srgb"];
+	int hslices = p_options["slices/horizontal"];
+	int vslices = p_options["slices/vertical"];
+
+	Ref<Image> image;
+	image.instance();
+	Error err = ImageLoader::load_image(p_source_file, image, NULL, false, 1.0);
+	if (err != OK)
+		return err;
+
+	int tex_flags = 0;
+	if (repeat > 0)
+		tex_flags |= Texture::FLAG_REPEAT;
+	if (repeat == 2)
+		tex_flags |= Texture::FLAG_MIRRORED_REPEAT;
+	if (filter)
+		tex_flags |= Texture::FLAG_FILTER;
+	if (mipmaps || compress_mode == COMPRESS_VIDEO_RAM)
+		tex_flags |= Texture::FLAG_MIPMAPS;
+	if (srgb == 1)
+		tex_flags |= Texture::FLAG_CONVERT_TO_LINEAR;
+
+	Vector<Ref<Image> > slices;
+
+	int slice_w = image->get_width() / hslices;
+	int slice_h = image->get_height() / vslices;
+
+	//optimize
+	if (compress_mode == COMPRESS_VIDEO_RAM) {
+		//if using video ram, optimize
+		if (srgb) {
+			//remove alpha if not needed, so compression is more efficient
+			if (image->get_format() == Image::FORMAT_RGBA8 && !image->detect_alpha()) {
+				image->convert(Image::FORMAT_RGB8);
+			}
+		} else {
+			image->optimize_channels();
+		}
+	}
+
+	for (int i = 0; i < vslices; i++) {
+		for (int j = 0; j < hslices; j++) {
+			int x = slice_w * j;
+			int y = slice_h * i;
+			Ref<Image> slice = image->get_rect(Rect2(x, y, slice_w, slice_h));
+			ERR_CONTINUE(slice.is_null() || slice->empty());
+			if (slice->get_width() != slice_w || slice->get_height() != slice_h) {
+				slice->resize(slice_w, slice_h);
+			}
+			slices.push_back(slice);
+		}
+	}
+
+	String extension = get_save_extension();
+
+	if (compress_mode == COMPRESS_VIDEO_RAM) {
+		//must import in all formats, in order of priority (so platform choses the best supported one. IE, etc2 over etc).
+		//Android, GLES 2.x
+
+		bool ok_on_pc = false;
+
+		if (ProjectSettings::get_singleton()->get("rendering/vram_compression/import_s3tc")) {
+
+			_save_tex(slices, p_save_path + ".s3tc." + extension, compress_mode, Image::COMPRESS_S3TC, mipmaps, tex_flags);
+			r_platform_variants->push_back("s3tc");
+			ok_on_pc = true;
+		}
+
+		if (ProjectSettings::get_singleton()->get("rendering/vram_compression/import_etc2")) {
+
+			_save_tex(slices, p_save_path + ".etc2." + extension, compress_mode, Image::COMPRESS_ETC2, mipmaps, tex_flags);
+			r_platform_variants->push_back("etc2");
+		}
+
+		if (ProjectSettings::get_singleton()->get("rendering/vram_compression/import_etc")) {
+			_save_tex(slices, p_save_path + ".etc." + extension, compress_mode, Image::COMPRESS_ETC, mipmaps, tex_flags);
+			r_platform_variants->push_back("etc");
+		}
+
+		if (ProjectSettings::get_singleton()->get("rendering/vram_compression/import_pvrtc")) {
+
+			_save_tex(slices, p_save_path + ".pvrtc." + extension, compress_mode, Image::COMPRESS_PVRTC4, mipmaps, tex_flags);
+			r_platform_variants->push_back("pvrtc");
+		}
+
+		if (!ok_on_pc) {
+			EditorNode::add_io_error("Warning, no suitable PC VRAM compression enabled in Project Settings. This texture will not display correcly on PC.");
+		}
+	} else {
+		//import normally
+		_save_tex(slices, p_save_path + "." + extension, compress_mode, Image::COMPRESS_S3TC /*this is ignored */, mipmaps, tex_flags);
+	}
+
+	return OK;
+}
+
+ResourceImporterLayeredTexture *ResourceImporterLayeredTexture::singleton = NULL;
+
+ResourceImporterLayeredTexture::ResourceImporterLayeredTexture() {
+
+	singleton = this;
+	is_3d = true;
+}
+
+ResourceImporterLayeredTexture::~ResourceImporterLayeredTexture() {
+}

+ 57 - 0
editor/import/resource_importer_layered_texture.h

@@ -0,0 +1,57 @@
+#ifndef RESOURCE_IMPORTER_LAYERED_TEXTURE_H
+#define RESOURCE_IMPORTER_LAYERED_TEXTURE_H
+
+#include "image.h"
+#include "io/resource_import.h"
+
+class StreamTexture;
+
+class ResourceImporterLayeredTexture : public ResourceImporter {
+	GDCLASS(ResourceImporterLayeredTexture, ResourceImporter)
+
+	bool is_3d;
+
+protected:
+	static void _texture_reimport_srgb(const Ref<StreamTexture> &p_tex);
+	static void _texture_reimport_3d(const Ref<StreamTexture> &p_tex);
+	static void _texture_reimport_normal(const Ref<StreamTexture> &p_tex);
+
+	static ResourceImporterLayeredTexture *singleton;
+
+public:
+	static ResourceImporterLayeredTexture *get_singleton() { return singleton; }
+	virtual String get_importer_name() const;
+	virtual String get_visible_name() const;
+	virtual void get_recognized_extensions(List<String> *p_extensions) const;
+	virtual String get_save_extension() const;
+	virtual String get_resource_type() const;
+
+	enum Preset {
+		PRESET_3D,
+		PRESET_2D,
+		PRESET_COLOR_CORRECT,
+	};
+
+	enum CompressMode {
+		COMPRESS_LOSSLESS,
+		COMPRESS_VIDEO_RAM,
+		COMPRESS_UNCOMPRESSED
+	};
+
+	virtual int get_preset_count() const;
+	virtual String get_preset_name(int p_idx) const;
+
+	virtual void get_import_options(List<ImportOption> *r_options, int p_preset = 0) const;
+	virtual bool get_option_visibility(const String &p_option, const Map<StringName, Variant> &p_options) const;
+
+	void _save_tex(const Vector<Ref<Image> > &p_images, const String &p_to_path, int p_compress_mode, Image::CompressMode p_vram_compression, bool p_mipmaps, int p_texture_flags);
+
+	virtual Error import(const String &p_source_file, const String &p_save_path, const Map<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files = NULL);
+
+	void update_imports();
+
+	void set_3d(bool p_3d) { is_3d = p_3d; }
+	ResourceImporterLayeredTexture();
+	~ResourceImporterLayeredTexture();
+};
+#endif // RESOURCE_IMPORTER_LAYERED_TEXTURE_H

+ 28 - 1
modules/etc/image_etc.cpp

@@ -98,6 +98,33 @@ static void _compress_etc(Image *p_img, float p_lossy_quality, bool force_etc1_f
 	Image::Format img_format = p_img->get_format();
 	Image::DetectChannels detected_channels = p_img->get_detected_channels();
 
+	if (p_source == Image::COMPRESS_SOURCE_LAYERED) {
+		//keep what comes in
+		switch (p_img->get_format()) {
+			case Image::FORMAT_L8: {
+				detected_channels = Image::DETECTED_L;
+			} break;
+			case Image::FORMAT_LA8: {
+				detected_channels = Image::DETECTED_LA;
+			} break;
+			case Image::FORMAT_R8: {
+				detected_channels = Image::DETECTED_R;
+			} break;
+			case Image::FORMAT_RG8: {
+				detected_channels = Image::DETECTED_RG;
+			} break;
+			case Image::FORMAT_RGB8: {
+				detected_channels = Image::DETECTED_RGB;
+			} break;
+			case Image::FORMAT_RGBA8:
+			case Image::FORMAT_RGBA4444:
+			case Image::FORMAT_RGBA5551: {
+				detected_channels = Image::DETECTED_RGBA;
+			} break;
+			default: {}
+		}
+	}
+
 	if (p_source == Image::COMPRESS_SOURCE_SRGB && (detected_channels == Image::DETECTED_R || detected_channels == Image::DETECTED_RG)) {
 		//R and RG do not support SRGB
 		detected_channels = Image::DETECTED_RGB;
@@ -147,7 +174,7 @@ static void _compress_etc(Image *p_img, float p_lossy_quality, bool force_etc1_f
 
 	PoolVector<uint8_t>::Read r = img->get_data().read();
 
-	int target_size = Image::get_image_data_size(imgw, imgh, etc_format, p_img->has_mipmaps() ? -1 : 0);
+	int target_size = Image::get_image_data_size(imgw, imgh, etc_format, p_img->has_mipmaps());
 	int mmc = 1 + (p_img->has_mipmaps() ? Image::get_image_required_mipmaps(imgw, imgh, etc_format) : 0);
 
 	PoolVector<uint8_t> dst_data;

+ 29 - 2
modules/squish/image_compress_squish.cpp

@@ -46,7 +46,7 @@ void image_decompress_squish(Image *p_image) {
 
 	Image::Format target_format = Image::FORMAT_RGBA8;
 	PoolVector<uint8_t> data;
-	int target_size = Image::get_image_data_size(w, h, target_format, p_image->has_mipmaps() ? -1 : 0);
+	int target_size = Image::get_image_data_size(w, h, target_format, p_image->has_mipmaps());
 	int mm_count = p_image->get_mipmap_count();
 	data.resize(target_size);
 
@@ -96,6 +96,33 @@ void image_compress_squish(Image *p_image, Image::CompressSource p_source) {
 
 		Image::DetectChannels dc = p_image->get_detected_channels();
 
+		if (p_source == Image::COMPRESS_SOURCE_LAYERED) {
+			//keep what comes in
+			switch (p_image->get_format()) {
+				case Image::FORMAT_L8: {
+					dc = Image::DETECTED_L;
+				} break;
+				case Image::FORMAT_LA8: {
+					dc = Image::DETECTED_LA;
+				} break;
+				case Image::FORMAT_R8: {
+					dc = Image::DETECTED_R;
+				} break;
+				case Image::FORMAT_RG8: {
+					dc = Image::DETECTED_RG;
+				} break;
+				case Image::FORMAT_RGB8: {
+					dc = Image::DETECTED_RGB;
+				} break;
+				case Image::FORMAT_RGBA8:
+				case Image::FORMAT_RGBA4444:
+				case Image::FORMAT_RGBA5551: {
+					dc = Image::DETECTED_RGBA;
+				} break;
+				default: {}
+			}
+		}
+
 		p_image->convert(Image::FORMAT_RGBA8); //still uses RGBA to convert
 
 		if (p_source == Image::COMPRESS_SOURCE_SRGB && (dc == Image::DETECTED_R || dc == Image::DETECTED_RG)) {
@@ -148,7 +175,7 @@ void image_compress_squish(Image *p_image, Image::CompressSource p_source) {
 		}
 
 		PoolVector<uint8_t> data;
-		int target_size = Image::get_image_data_size(w, h, target_format, p_image->has_mipmaps() ? -1 : 0);
+		int target_size = Image::get_image_data_size(w, h, target_format, p_image->has_mipmaps());
 		int mm_count = p_image->has_mipmaps() ? Image::get_image_required_mipmaps(w, h, target_format) : 0;
 		data.resize(target_size);
 		int shift = Image::get_format_pixel_rshift(target_format);

+ 7 - 0
scene/register_scene_types.cpp

@@ -217,6 +217,7 @@ static ResourceFormatLoaderText *resource_loader_text = NULL;
 static ResourceFormatLoaderDynamicFont *resource_loader_dynamic_font = NULL;
 
 static ResourceFormatLoaderStreamTexture *resource_loader_stream_texture = NULL;
+static ResourceFormatLoaderTextureLayered *resource_loader_texture_layered = NULL;
 
 static ResourceFormatLoaderBMFont *resource_loader_bmfont = NULL;
 
@@ -237,6 +238,9 @@ void register_scene_types() {
 	resource_loader_stream_texture = memnew(ResourceFormatLoaderStreamTexture);
 	ResourceLoader::add_resource_format_loader(resource_loader_stream_texture);
 
+	resource_loader_texture_layered = memnew(ResourceFormatLoaderTextureLayered);
+	ResourceLoader::add_resource_format_loader(resource_loader_texture_layered);
+
 	resource_loader_theme = memnew(ResourceFormatLoaderTheme);
 	ResourceLoader::add_resource_format_loader(resource_loader_theme);
 
@@ -615,7 +619,9 @@ void register_scene_types() {
 	ClassDB::register_class<ProxyTexture>();
 	ClassDB::register_class<AnimatedTexture>();
 	ClassDB::register_class<CubeMap>();
+	ClassDB::register_virtual_class<TextureLayered>();
 	ClassDB::register_class<Texture3D>();
+	ClassDB::register_class<TextureArray>();
 	ClassDB::register_class<Animation>();
 	ClassDB::register_virtual_class<Font>();
 	ClassDB::register_class<BitmapFont>();
@@ -729,6 +735,7 @@ void unregister_scene_types() {
 
 	memdelete(resource_loader_dynamic_font);
 	memdelete(resource_loader_stream_texture);
+	memdelete(resource_loader_texture_layered);
 	memdelete(resource_loader_theme);
 
 	DynamicFont::finish_dynamic_fonts();

+ 251 - 149
scene/resources/texture.cpp

@@ -151,13 +151,6 @@ bool ImageTexture::_get(const StringName &p_name, Variant &r_ret) const {
 
 void ImageTexture::_get_property_list(List<PropertyInfo> *p_list) const {
 
-	PropertyHint img_hint = PROPERTY_HINT_NONE;
-	if (storage == STORAGE_COMPRESS_LOSSY) {
-		img_hint = PROPERTY_HINT_IMAGE_COMPRESS_LOSSY;
-	} else if (storage == STORAGE_COMPRESS_LOSSLESS) {
-		img_hint = PROPERTY_HINT_IMAGE_COMPRESS_LOSSLESS;
-	}
-
 	p_list->push_back(PropertyInfo(Variant::INT, "flags", PROPERTY_HINT_FLAGS, "Mipmaps,Repeat,Filter,Anisotropic,sRGB,Mirrored Repeat"));
 	p_list->push_back(PropertyInfo(Variant::OBJECT, "image", PROPERTY_HINT_RESOURCE_TYPE, "Image"));
 	p_list->push_back(PropertyInfo(Variant::VECTOR2, "size", PROPERTY_HINT_NONE, ""));
@@ -592,7 +585,7 @@ Error StreamTexture::_load_data(const String &p_path, int &tw, int &th, int &fla
 			int sh = th;
 
 			int mipmaps = Image::get_image_required_mipmaps(tw, th, format);
-			int total_size = Image::get_image_data_size(tw, th, format, mipmaps);
+			int total_size = Image::get_image_data_size(tw, th, format, true);
 			int idx = 0;
 			int ofs = 0;
 
@@ -1155,7 +1148,6 @@ void LargeTexture::draw_rect(RID p_canvas_item, const Rect2 &p_rect, bool p_tile
 
 	Size2 scale = p_rect.size / size;
 
-	RID normal_rid = p_normal_map.is_valid() ? p_normal_map->get_rid() : RID();
 	for (int i = 0; i < pieces.size(); i++) {
 
 		// TODO
@@ -1170,7 +1162,6 @@ void LargeTexture::draw_rect_region(RID p_canvas_item, const Rect2 &p_rect, cons
 
 	Size2 scale = p_rect.size / p_src_rect.size;
 
-	RID normal_rid = p_normal_map.is_valid() ? p_normal_map->get_rid() : RID();
 	for (int i = 0; i < pieces.size(); i++) {
 
 		// TODO
@@ -1322,13 +1313,6 @@ bool CubeMap::_get(const StringName &p_name, Variant &r_ret) const {
 
 void CubeMap::_get_property_list(List<PropertyInfo> *p_list) const {
 
-	PropertyHint img_hint = PROPERTY_HINT_NONE;
-	if (storage == STORAGE_COMPRESS_LOSSY) {
-		img_hint = PROPERTY_HINT_IMAGE_COMPRESS_LOSSY;
-	} else if (storage == STORAGE_COMPRESS_LOSSLESS) {
-		img_hint = PROPERTY_HINT_IMAGE_COMPRESS_LOSSLESS;
-	}
-
 	p_list->push_back(PropertyInfo(Variant::OBJECT, "side/left", PROPERTY_HINT_RESOURCE_TYPE, "Image"));
 	p_list->push_back(PropertyInfo(Variant::OBJECT, "side/right", PROPERTY_HINT_RESOURCE_TYPE, "Image"));
 	p_list->push_back(PropertyInfo(Variant::OBJECT, "side/bottom", PROPERTY_HINT_RESOURCE_TYPE, "Image"));
@@ -1876,209 +1860,327 @@ AnimatedTexture::AnimatedTexture() {
 AnimatedTexture::~AnimatedTexture() {
 	VS::get_singleton()->free(proxy);
 }
+///////////////////////////////
+
+void TextureLayered::set_flags(uint32_t p_flags) {
+	flags = p_flags;
 
-bool Texture3D::get_split_single_image_enabled() const {
-	return split_single_image_enabled;
+	if (texture.is_valid()) {
+		VS::get_singleton()->texture_set_flags(texture, flags);
+	}
 }
 
-void Texture3D::set_split_single_image_enabled(bool p_split_enabled) {
-	split_single_image_enabled = p_split_enabled;
+uint32_t TextureLayered::get_flags() const {
+	return flags;
+}
 
-	_change_notify();
+Image::Format TextureLayered::get_format() const {
+	return format;
 }
 
-uint32_t Texture3D::get_split_single_image_h_split() const {
-	return split_single_image_h_split;
+uint32_t TextureLayered::get_width() const {
+	return width;
 }
 
-void Texture3D::set_split_single_image_h_split(uint32_t p_h_split) {
-	split_single_image_h_split = p_h_split;
+uint32_t TextureLayered::get_height() const {
+	return height;
+}
 
-	if (split_single_image_image.is_valid())
-		create_from_image(split_single_image_image, split_single_image_h_split, split_single_image_v_split, split_single_image_num_layers, flags);
+uint32_t TextureLayered::get_depth() const {
+	return depth;
 }
 
-uint32_t Texture3D::get_split_single_image_v_split() const {
-	return split_single_image_v_split;
+void TextureLayered::_set_data(const Dictionary &p_data) {
+	ERR_FAIL_COND(!p_data.has("width"));
+	ERR_FAIL_COND(!p_data.has("height"));
+	ERR_FAIL_COND(!p_data.has("depth"));
+	ERR_FAIL_COND(!p_data.has("format"));
+	ERR_FAIL_COND(!p_data.has("flags"));
+	ERR_FAIL_COND(!p_data.has("layers"));
+	int w = p_data["width"];
+	int h = p_data["height"];
+	int d = p_data["depth"];
+	Image::Format format = Image::Format(int(p_data["format"]));
+	int flags = p_data["flags"];
+	Array layers = p_data["layers"];
+	ERR_FAIL_COND(layers.size() != d);
+
+	create(w, h, d, format, flags);
+
+	for (int i = 0; i < layers.size(); i++) {
+		Ref<Image> img = layers[i];
+		ERR_CONTINUE(!img.is_valid());
+		ERR_CONTINUE(img->get_format() != format);
+		ERR_CONTINUE(img->get_width() != w);
+		ERR_CONTINUE(img->get_height() != h);
+		set_layer_data(img, i);
+	}
 }
 
-void Texture3D::set_split_single_image_v_split(uint32_t p_v_split) {
-	split_single_image_v_split = p_v_split;
+Dictionary TextureLayered::_get_data() const {
+	Dictionary d;
+	d["width"] = width;
+	d["height"] = height;
+	d["depth"] = depth;
+	d["flags"] = flags;
+	d["format"] = format;
 
-	if (split_single_image_image.is_valid())
-		create_from_image(split_single_image_image, split_single_image_h_split, split_single_image_v_split, split_single_image_num_layers, flags);
+	Array layers;
+	for (int i = 0; i < depth; i++) {
+		layers.push_back(get_layer_data(i));
+	}
+	d["layers"] = layers;
+	return d;
 }
 
-uint32_t Texture3D::get_split_single_image_num_layers() const {
-	return split_single_image_num_layers;
-}
+void TextureLayered::create(uint32_t p_width, uint32_t p_height, uint32_t p_depth, Image::Format p_format, uint32_t p_flags) {
+	VS::get_singleton()->texture_allocate(texture, p_width, p_height, p_depth, p_format, is_3d ? VS::TEXTURE_TYPE_3D : VS::TEXTURE_TYPE_2D_ARRAY, p_flags);
 
-void Texture3D::set_split_single_image_num_layers(uint32_t p_num_layers) {
-	split_single_image_num_layers = p_num_layers;
+	width = p_width;
+	height = p_height;
+	depth = p_depth;
 
-	if (split_single_image_image.is_valid())
-		create_from_image(split_single_image_image, split_single_image_h_split, split_single_image_v_split, split_single_image_num_layers, flags);
+	flags = p_flags;
 }
 
-Ref<Image> Texture3D::get_split_single_image_image() const {
-	return split_single_image_image;
+void TextureLayered::set_layer_data(const Ref<Image> &p_image, int p_layer) {
+	ERR_FAIL_COND(!texture.is_valid());
+	VS::get_singleton()->texture_set_data(texture, p_image, p_layer);
 }
 
-void Texture3D::set_split_single_image_image(const Ref<Image> &p_image) {
-	split_single_image_image = p_image;
+Ref<Image> TextureLayered::get_layer_data(int p_layer) const {
 
-	create_from_image(split_single_image_image, split_single_image_h_split, split_single_image_v_split, split_single_image_num_layers, flags);
+	ERR_FAIL_COND_V(!texture.is_valid(), Ref<Image>());
+	return VS::get_singleton()->texture_get_data(texture, p_layer);
 }
 
-void Texture3D::_validate_property(PropertyInfo &property) const {
-	if (property.name.begins_with("split_single_image_") && property.name != "split_single_image_enabled" && !split_single_image_enabled) {
-		property.usage = 0;
-	}
+void TextureLayered::set_data_partial(const Ref<Image> &p_image, int p_x_ofs, int p_y_ofs, int p_z, int p_mipmap) {
+	ERR_FAIL_COND(!texture.is_valid());
+	VS::get_singleton()->texture_set_data_partial(texture, p_image, 0, 0, p_image->get_width(), p_image->get_height(), p_x_ofs, p_y_ofs, p_mipmap, p_z);
 }
 
-void Texture3D::_bind_methods() {
-	ClassDB::bind_method(D_METHOD("set_flags", "flags"), &Texture3D::set_flags);
-	ClassDB::bind_method(D_METHOD("get_flags"), &Texture3D::get_flags);
-
-	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);
+RID TextureLayered::get_rid() const {
+	return texture;
+}
 
-	ClassDB::bind_method(D_METHOD("set_split_single_image_enabled", "split_enabled"), &Texture3D::set_split_single_image_enabled);
-	ClassDB::bind_method(D_METHOD("get_split_single_image_enabled"), &Texture3D::get_split_single_image_enabled);
+void TextureLayered::set_path(const String &p_path, bool p_take_over) {
+	if (texture.is_valid()) {
+		VS::get_singleton()->texture_set_path(texture, p_path);
+	}
 
-	ClassDB::bind_method(D_METHOD("set_split_single_image_h_split", "h_split"), &Texture3D::set_split_single_image_h_split);
-	ClassDB::bind_method(D_METHOD("get_split_single_image_h_split"), &Texture3D::get_split_single_image_h_split);
+	Resource::set_path(p_path, p_take_over);
+}
 
-	ClassDB::bind_method(D_METHOD("set_split_single_image_v_split", "v_split"), &Texture3D::set_split_single_image_v_split);
-	ClassDB::bind_method(D_METHOD("get_split_single_image_v_split"), &Texture3D::get_split_single_image_v_split);
+void TextureLayered::_bind_methods() {
+	ClassDB::bind_method(D_METHOD("set_flags", "flags"), &TextureLayered::set_flags);
+	ClassDB::bind_method(D_METHOD("get_flags"), &TextureLayered::get_flags);
 
-	ClassDB::bind_method(D_METHOD("set_split_single_image_num_layers", "num_layers"), &Texture3D::set_split_single_image_num_layers);
-	ClassDB::bind_method(D_METHOD("get_split_single_image_num_layers"), &Texture3D::get_split_single_image_num_layers);
+	ClassDB::bind_method(D_METHOD("get_format"), &TextureLayered::get_format);
 
-	ClassDB::bind_method(D_METHOD("set_split_single_image_image", "image"), &Texture3D::set_split_single_image_image);
-	ClassDB::bind_method(D_METHOD("get_split_single_image_image"), &Texture3D::get_split_single_image_image);
+	ClassDB::bind_method(D_METHOD("get_width"), &TextureLayered::get_width);
+	ClassDB::bind_method(D_METHOD("get_height"), &TextureLayered::get_height);
+	ClassDB::bind_method(D_METHOD("get_depth"), &TextureLayered::get_depth);
 
-	ClassDB::bind_method(D_METHOD("create", "width", "height", "depth", "format", "flags"), &Texture3D::create, DEFVAL(FLAGS_DEFAULT));
-	ClassDB::bind_method(D_METHOD("create_from_image", "image", "h_split", "v_split", "num_layer", "flags"), &Texture3D::create_from_image, DEFVAL(FLAGS_DEFAULT));
+	ClassDB::bind_method(D_METHOD("create", "width", "height", "depth", "format", "flags"), &TextureLayered::create, DEFVAL(FLAGS_DEFAULT));
+	ClassDB::bind_method(D_METHOD("set_layer_data", "image", "layer"), &TextureLayered::set_layer_data);
+	ClassDB::bind_method(D_METHOD("get_layer_data", "layer"), &TextureLayered::set_layer_data);
+	ClassDB::bind_method(D_METHOD("set_data_partial", "image", "x_offset", "y_offset", "layer", "mipmap"), &TextureLayered::set_data_partial, DEFVAL(0));
 
-	ClassDB::bind_method(D_METHOD("set_data_partial", "image", "x_offset", "y_offset", "layer", "mipmap"), &Texture3D::set_data_partial, DEFVAL(0));
+	ClassDB::bind_method(D_METHOD("_set_data", "data"), &TextureLayered::_set_data);
+	ClassDB::bind_method(D_METHOD("_get_data"), &TextureLayered::_get_data);
 
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "flags", PROPERTY_HINT_FLAGS, "Mipmaps,Repeat,Filter"), "set_flags", "get_flags");
+	ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "_set_data", "_get_data");
 
 	BIND_ENUM_CONSTANT(FLAG_MIPMAPS);
 	BIND_ENUM_CONSTANT(FLAG_REPEAT);
 	BIND_ENUM_CONSTANT(FLAG_FILTER);
 	BIND_ENUM_CONSTANT(FLAGS_DEFAULT);
-
-	ADD_GROUP("Split single image", "split_single_image_");
-	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "split_single_image_enabled"), "set_split_single_image_enabled", "get_split_single_image_enabled");
-	ADD_PROPERTY(PropertyInfo(Variant::INT, "split_single_image_h_split", PROPERTY_HINT_RANGE, "0,1000"), "set_split_single_image_h_split", "get_split_single_image_h_split");
-	ADD_PROPERTY(PropertyInfo(Variant::INT, "split_single_image_v_split", PROPERTY_HINT_RANGE, "0,1000"), "set_split_single_image_v_split", "get_split_single_image_v_split");
-	ADD_PROPERTY(PropertyInfo(Variant::INT, "split_single_image_num_layers"), "set_split_single_image_num_layers", "get_split_single_image_num_layers");
-	ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "split_single_image_image", PROPERTY_HINT_RESOURCE_TYPE, "Image"), "set_split_single_image_image", "get_split_single_image_image");
 }
 
-void Texture3D::set_flags(uint32_t p_flags) {
-	flags = p_flags;
+TextureLayered::TextureLayered(bool p_3d) {
+	is_3d = p_3d;
+	format = Image::FORMAT_MAX;
+	flags = FLAGS_DEFAULT;
+
+	width = 0;
+	height = 0;
+	depth = 0;
+
+	texture = VS::get_singleton()->texture_create();
+}
 
+TextureLayered::~TextureLayered() {
 	if (texture.is_valid()) {
-		VS::get_singleton()->texture_set_flags(texture, flags);
+		VS::get_singleton()->free(texture);
 	}
 }
 
-uint32_t Texture3D::get_flags() const {
-	return flags;
-}
+RES ResourceFormatLoaderTextureLayered::load(const String &p_path, const String &p_original_path, Error *r_error) {
 
-Image::Format Texture3D::get_format() const {
-	return format;
-}
+	if (r_error) {
+		*r_error = ERR_CANT_OPEN;
+	}
 
-uint32_t Texture3D::get_width() const {
-	return width;
-}
+	Ref<TextureLayered> lt;
+	Ref<Texture3D> tex3d;
+	Ref<TextureArray> texarr;
 
-uint32_t Texture3D::get_height() const {
-	return height;
-}
+	if (p_path.ends_with("tex3d")) {
+		tex3d.instance();
+		lt = tex3d;
+	} else if (p_path.ends_with("texarr")) {
+		texarr.instance();
+		lt = texarr;
+	} else {
+		ERR_EXPLAIN("Unrecognized layered texture extension");
+		ERR_FAIL_V(RES());
+	}
 
-uint32_t Texture3D::get_depth() const {
-	return depth;
-}
+	FileAccess *f = FileAccess::open(p_path, FileAccess::READ);
+	ERR_FAIL_COND_V(!f, RES());
 
-void Texture3D::create(uint32_t p_width, uint32_t p_height, uint32_t p_depth, Image::Format p_format, uint32_t p_flags) {
-	VS::get_singleton()->texture_allocate(texture, p_width, p_height, p_depth, p_format, VS::TEXTURE_TYPE_3D, p_flags);
+	uint8_t header[5] = { 0, 0, 0, 0, 0 };
+	f->get_buffer(header, 4);
 
-	width = p_width;
-	height = p_height;
-	depth = p_depth;
+	if (header[0] == 'G' && header[1] == 'D' && header[2] == '3' && header[3] == 'T') {
+		if (tex3d.is_null()) {
+			memdelete(f);
+			ERR_FAIL_COND_V(tex3d.is_null(), RES())
+		}
+	} else if (header[0] == 'G' && header[1] == 'D' && header[2] == 'A' && header[3] == 'T') {
+		if (texarr.is_null()) {
+			memdelete(f);
+			ERR_FAIL_COND_V(texarr.is_null(), RES())
+		}
+	} else {
 
-	flags = p_flags;
-}
+		ERR_EXPLAIN("Unrecognized layered texture file format: " + String((const char *)header));
+		ERR_FAIL_V(RES());
+	}
 
-void Texture3D::create_from_image(const Ref<Image> &p_image, uint32_t p_h_split, uint32_t p_v_split, uint32_t p_num_layer, uint32_t flags) {
+	int tw = f->get_32();
+	int th = f->get_32();
+	int td = f->get_32();
+	int flags = f->get_32(); //texture flags!
+	Image::Format format = Image::Format(f->get_32());
+	uint32_t compression = f->get_32(); // 0 - lossless (PNG), 1 - vram, 2 - uncompressed
 
-	ERR_FAIL_COND(p_image.is_null());
-	ERR_FAIL_COND((p_h_split + 1) * (p_v_split + 1) < p_num_layer);
+	lt->create(tw, th, td, format, flags);
 
-	uint32_t total_width = p_image->get_width();
-	uint32_t total_height = p_image->get_height();
+	for (int layer = 0; layer < td; layer++) {
 
-	uint32_t width = total_width / (p_h_split + 1);
-	uint32_t height = total_height / (p_v_split + 1);
+		Ref<Image> image;
+		image.instance();
 
-	create(width, height, p_num_layer, p_image->get_format(), flags);
+		if (compression == COMPRESSION_LOSSLESS) {
+			//look for a PNG file inside
 
-	for (uint32_t i = 0; i < p_num_layer; i++) {
-		uint32_t row = i / (p_v_split + 1);
-		uint32_t col = i % (p_v_split + 1);
+			int mipmaps = f->get_32();
+			Vector<Ref<Image> > mipmap_images;
 
-		uint32_t x_offset = col * width;
-		uint32_t y_offset = row * height;
+			for (int i = 0; i < mipmaps; i++) {
+				uint32_t size = f->get_32();
 
-		VS::get_singleton()->texture_set_data_partial(texture, p_image, x_offset, y_offset, width, height, 0, 0, 0, i);
-	}
-}
+				PoolVector<uint8_t> pv;
+				pv.resize(size);
+				{
+					PoolVector<uint8_t>::Write w = pv.write();
+					f->get_buffer(w.ptr(), size);
+				}
 
-void Texture3D::set_data_partial(const Ref<Image> &p_image, int p_x_ofs, int p_y_ofs, int p_layer, int p_mipmap) {
-	ERR_FAIL_COND(!texture.is_valid());
-	VS::get_singleton()->texture_set_data_partial(texture, p_image, 0, 0, p_image->get_width(), p_image->get_height(), p_x_ofs, p_y_ofs, p_mipmap, p_layer);
-}
+				Ref<Image> img = Image::lossless_unpacker(pv);
 
-RID Texture3D::get_rid() const {
-	return texture;
-}
+				if (img.is_null() || img->empty() || format != img->get_format()) {
+					if (r_error) {
+						*r_error = ERR_FILE_CORRUPT;
+					}
+					memdelete(f);
+					ERR_FAIL_V(RES());
+				}
 
-void Texture3D::set_path(const String &p_path, bool p_take_over) {
-	if (texture.is_valid()) {
-		VS::get_singleton()->texture_set_path(texture, p_path);
-	}
+				mipmap_images.push_back(img);
+			}
 
-	Resource::set_path(p_path, p_take_over);
-}
+			if (mipmap_images.size() == 1) {
 
-Texture3D::Texture3D() {
-	format = Image::FORMAT_MAX;
-	flags = FLAGS_DEFAULT;
+				image = mipmap_images[0];
 
-	width = 0;
-	height = 0;
-	depth = 0;
+			} else {
+				int total_size = Image::get_image_data_size(tw, th, format, true);
+				PoolVector<uint8_t> img_data;
+				img_data.resize(total_size);
+
+				{
+					PoolVector<uint8_t>::Write w = img_data.write();
+
+					int ofs = 0;
+					for (int i = 0; i < mipmap_images.size(); i++) {
+
+						PoolVector<uint8_t> id = mipmap_images[i]->get_data();
+						int len = id.size();
+						PoolVector<uint8_t>::Read r = id.read();
+						copymem(&w[ofs], r.ptr(), len);
+						ofs += len;
+					}
+				}
 
-	split_single_image_enabled = false;
-	split_single_image_h_split = 0;
-	split_single_image_v_split = 0;
-	split_single_image_num_layers = 0;
-	split_single_image_image = Ref<Image>();
+				image->create(tw, th, true, format, img_data);
+				if (image->empty()) {
+					if (r_error) {
+						*r_error = ERR_FILE_CORRUPT;
+					}
+					memdelete(f);
+					ERR_FAIL_V(RES());
+				}
+			}
 
-	texture = VS::get_singleton()->texture_create();
-}
+		} else {
 
-Texture3D::~Texture3D() {
-	if (texture.is_valid()) {
-		VS::get_singleton()->free(texture);
+			//look for regular format
+			bool mipmaps = (flags & Texture::FLAG_MIPMAPS);
+			int total_size = Image::get_image_data_size(tw, th, format, mipmaps);
+
+			PoolVector<uint8_t> img_data;
+			img_data.resize(total_size);
+
+			{
+				PoolVector<uint8_t>::Write w = img_data.write();
+				int bytes = f->get_buffer(w.ptr(), total_size);
+				if (bytes != total_size) {
+					if (r_error) {
+						*r_error = ERR_FILE_CORRUPT;
+						memdelete(f);
+					}
+					ERR_FAIL_V(RES());
+				}
+			}
+
+			image->create(tw, th, mipmaps, format, img_data);
+		}
+
+		lt->set_layer_data(image, layer);
 	}
+
+	if (r_error)
+		*r_error = OK;
+
+	return lt;
+}
+
+void ResourceFormatLoaderTextureLayered::get_recognized_extensions(List<String> *p_extensions) const {
+
+	p_extensions->push_back("tex3d");
+	p_extensions->push_back("texarr");
+}
+bool ResourceFormatLoaderTextureLayered::handles_type(const String &p_type) const {
+	return p_type == "Texture3D" || p_type == "TextureArray";
+}
+String ResourceFormatLoaderTextureLayered::get_resource_type(const String &p_path) const {
+
+	if (p_path.get_extension().to_lower() == "tex3d")
+		return "Texture3D";
+	if (p_path.get_extension().to_lower() == "texarr")
+		return "TextureArray";
+	return "";
 }

+ 43 - 32
scene/resources/texture.h

@@ -401,39 +401,33 @@ VARIANT_ENUM_CAST(CubeMap::Flags)
 VARIANT_ENUM_CAST(CubeMap::Side)
 VARIANT_ENUM_CAST(CubeMap::Storage)
 
-class Texture3D : public Resource {
+class TextureLayered : public Resource {
 
-	GDCLASS(Texture3D, Resource)
-	RES_BASE_EXTENSION("tex3d")
+	GDCLASS(TextureLayered, Resource)
 
 public:
 	enum Flags {
 		FLAG_MIPMAPS = VisualServer::TEXTURE_FLAG_MIPMAPS,
 		FLAG_REPEAT = VisualServer::TEXTURE_FLAG_REPEAT,
 		FLAG_FILTER = VisualServer::TEXTURE_FLAG_FILTER,
-		FLAGS_DEFAULT = FLAG_REPEAT | FLAG_FILTER,
+		FLAG_CONVERT_TO_LINEAR = VisualServer::TEXTURE_FLAG_CONVERT_TO_LINEAR,
+		FLAGS_DEFAULT = FLAG_FILTER,
 	};
 
 private:
+	bool is_3d;
 	RID texture;
 	Image::Format format;
 	uint32_t flags;
 
-	uint32_t width;
-	uint32_t height;
-	uint32_t depth;
-
-	// for setting an image as a property
-	bool split_single_image_enabled;
+	int width;
+	int height;
+	int depth;
 
-	uint32_t split_single_image_h_split;
-	uint32_t split_single_image_v_split;
-	uint32_t split_single_image_num_layers;
-	Ref<Image> split_single_image_image;
+	void _set_data(const Dictionary &p_data);
+	Dictionary _get_data() const;
 
 protected:
-	void _validate_property(PropertyInfo &property) const;
-
 	static void _bind_methods();
 
 public:
@@ -446,30 +440,47 @@ public:
 	uint32_t get_depth() const;
 
 	void create(uint32_t p_width, uint32_t p_height, uint32_t p_depth, Image::Format p_format, uint32_t p_flags = FLAGS_DEFAULT);
-	void create_from_image(const Ref<Image> &p_image, uint32_t p_h_split, uint32_t p_v_split, uint32_t p_num_layer, uint32_t flags = FLAGS_DEFAULT);
-
-	void set_data_partial(const Ref<Image> &p_image, int p_x_ofs, int p_y_ofs, int p_layer, int p_mipmap = 0);
+	void set_layer_data(const Ref<Image> &p_image, int p_layer);
+	Ref<Image> get_layer_data(int p_layer) const;
+	void set_data_partial(const Ref<Image> &p_image, int p_x_ofs, int p_y_ofs, int p_z, int p_mipmap = 0);
 
 	virtual RID get_rid() const;
+	virtual void set_path(const String &p_path, bool p_take_over = false);
 
-	bool get_split_single_image_enabled() const;
-	void set_split_single_image_enabled(bool p_split_enabled);
+	TextureLayered(bool p_3d = false);
+	~TextureLayered();
+};
 
-	uint32_t get_split_single_image_h_split() const;
-	void set_split_single_image_h_split(uint32_t p_h_split);
-	uint32_t get_split_single_image_v_split() const;
-	void set_split_single_image_v_split(uint32_t p_v_split);
+VARIANT_ENUM_CAST(TextureLayered::Flags)
 
-	uint32_t get_split_single_image_num_layers() const;
-	void set_split_single_image_num_layers(uint32_t p_num_layers);
+class Texture3D : public TextureLayered {
 
-	Ref<Image> get_split_single_image_image() const;
-	void set_split_single_image_image(const Ref<Image> &p_image);
+	GDCLASS(Texture3D, TextureLayered)
+public:
+	Texture3D() :
+			TextureLayered(true) {}
+};
 
-	virtual void set_path(const String &p_path, bool p_take_over = false);
+class TextureArray : public TextureLayered {
 
-	Texture3D();
-	~Texture3D();
+	GDCLASS(TextureArray, TextureLayered)
+public:
+	TextureArray() :
+			TextureLayered(false) {}
+};
+
+class ResourceFormatLoaderTextureLayered : public ResourceFormatLoader {
+public:
+	enum Compression {
+		COMPRESSION_LOSSLESS,
+		COMPRESSION_VRAM,
+		COMPRESSION_UNCOMPRESSED
+	};
+
+	virtual RES load(const String &p_path, const String &p_original_path = "", Error *r_error = NULL);
+	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 Texture {