From 45f74ceb853a2986acbdb44f7b36b409b4e0cea5 Mon Sep 17 00:00:00 2001 From: reduz Date: Thu, 24 Mar 2022 18:18:55 +0100 Subject: Add PortableCompressedTexture * Resource that allows saving textures embedded in scenes or standalone. * Supports only formats that are portable: Lossy, Lossles or BasisUniversal This is something I wanted to add for a long time. I made it now because @fire requires it for importing GLTF2 files with embedded textures, but also this will allow saving Godot scenes as standalone binary files that will run in all platforms (because textures will load everywhere). This is ideal when you want to distribute individual standalone assets online in games that can be built from Godot scenes. --- core/io/image.cpp | 7 +- core/io/image.h | 3 +- doc/classes/Image.xml | 2 +- doc/classes/PortableCompressedTexture2D.xml | 79 +++++++ editor/editor_node.cpp | 1 + modules/basis_universal/register_types.cpp | 17 +- scene/register_scene_types.cpp | 1 + scene/resources/texture.cpp | 308 +++++++++++++++++++++++++++- scene/resources/texture.h | 72 +++++++ 9 files changed, 482 insertions(+), 8 deletions(-) create mode 100644 doc/classes/PortableCompressedTexture2D.xml diff --git a/core/io/image.cpp b/core/io/image.cpp index 67e6724e71..fad9942017 100644 --- a/core/io/image.cpp +++ b/core/io/image.cpp @@ -2726,6 +2726,7 @@ Vector (*Image::png_packer)(const Ref &) = nullptr; Ref (*Image::png_unpacker)(const Vector &) = nullptr; Vector (*Image::basis_universal_packer)(const Ref &, Image::UsedChannels) = nullptr; Ref (*Image::basis_universal_unpacker)(const Vector &) = nullptr; +Ref (*Image::basis_universal_unpacker_ptr)(const uint8_t *, int) = nullptr; void Image::_set_data(const Dictionary &p_data) { ERR_FAIL_COND(!p_data.has("width")); @@ -3008,7 +3009,7 @@ void Image::adjust_bcs(float p_brightness, float p_contrast, float p_saturation) } } -Image::UsedChannels Image::detect_used_channels(CompressSource p_source) { +Image::UsedChannels Image::detect_used_channels(CompressSource p_source) const { ERR_FAIL_COND_V(data.size() == 0, USED_CHANNELS_RGBA); ERR_FAIL_COND_V(is_compressed(), USED_CHANNELS_RGBA); bool r = false, g = false, b = false, a = false, c = false; @@ -3613,6 +3614,10 @@ Image::Image(const uint8_t *p_mem_png_jpg, int p_len) { if (is_empty() && _jpg_mem_loader_func) { copy_internals_from(_jpg_mem_loader_func(p_mem_png_jpg, p_len)); } + + if (is_empty() && _webp_mem_loader_func) { + copy_internals_from(_webp_mem_loader_func(p_mem_png_jpg, p_len)); + } } Ref Image::duplicate(bool p_subresources) const { diff --git a/core/io/image.h b/core/io/image.h index 39c700565b..7e1e853244 100644 --- a/core/io/image.h +++ b/core/io/image.h @@ -147,6 +147,7 @@ public: static Ref (*png_unpacker)(const Vector &p_buffer); static Vector (*basis_universal_packer)(const Ref &p_image, UsedChannels p_channels); static Ref (*basis_universal_unpacker)(const Vector &p_buffer); + static Ref (*basis_universal_unpacker_ptr)(const uint8_t *p_data, int p_size); _FORCE_INLINE_ Color _get_color_at_ofs(const uint8_t *ptr, uint32_t ofs) const; _FORCE_INLINE_ void _set_color_at_ofs(uint8_t *ptr, uint32_t ofs, const Color &p_color); @@ -379,7 +380,7 @@ public: virtual Ref duplicate(bool p_subresources = false) const override; - UsedChannels detect_used_channels(CompressSource p_source = COMPRESS_SOURCE_GENERIC); + UsedChannels detect_used_channels(CompressSource p_source = COMPRESS_SOURCE_GENERIC) const; void optimize_channels(); Color get_pixelv(const Point2i &p_point) const; diff --git a/doc/classes/Image.xml b/doc/classes/Image.xml index 35868563de..140015babf 100644 --- a/doc/classes/Image.xml +++ b/doc/classes/Image.xml @@ -153,7 +153,7 @@ Returns [constant ALPHA_BLEND] if the image has data for alpha values. Returns [constant ALPHA_BIT] if all the alpha values are stored in a single bit. Returns [constant ALPHA_NONE] if no data for alpha values is found. - + diff --git a/doc/classes/PortableCompressedTexture2D.xml b/doc/classes/PortableCompressedTexture2D.xml new file mode 100644 index 0000000000..7b46a0bf07 --- /dev/null +++ b/doc/classes/PortableCompressedTexture2D.xml @@ -0,0 +1,79 @@ + + + + Provides a compressed texture for disk and/or VRAM in a way that is portable. + + + This class allows storing compressed textures as self contained (not imported) resources. + For 2D usage (compressed on disk, uncompressed on VRAM), the lossy and lossless modes are recommended. For 3D usage (compressed on VRAM) it depends on the target platform. + If you intend to only use desktop, S3TC or BPTC are recommended. For only mobile, ETC2 is recommended. + For portable, self contained 3D textures that work on both desktop and mobile, Basis Universal is recommended (although it has a small quality cost and longer compression time as a tradeoff). + This resource is intended to be created from code. + + + + + + + + + + + + Initializes the compressed texture from a base image. The compression mode must be provided. + If this image will be used as a normal map, the "normal map" flag is recommended, to ensure optimum quality. + If lossy compression is requested, the quality setting can optionally be provided. This maps to Lossy WEBP compression quality. + + + + + + Return the compression mode used (valid after initialized). + + + + + + Return the image format used (valid after initialized). + + + + + + Return whether the flag is overriden for all textures of this type. + + + + + + + Overrides the flag globally for all textures of this type. This is used primarly by the editor. + + + + + + + + When running on the editor, this class will keep the source compressed data in memory. Otherwise, the source compressed data is lost after loading and the resource can't be re saved. + This flag allows to keep the compressed data in memory if you intend it to persist after loading. + + + Allow overriding the texture size (for 2D only). + + + + + + + + + + + + + + + + + diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index bcdd54edd4..3249477d97 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -5852,6 +5852,7 @@ EditorNode::EditorNode() { EditorPropertyNameProcessor *epnp = memnew(EditorPropertyNameProcessor); add_child(epnp); + PortableCompressedTexture2D::set_keep_all_compressed_buffers(true); Input::get_singleton()->set_use_accumulated_input(true); Resource::_get_local_scene_func = _resource_get_edited_scene; diff --git a/modules/basis_universal/register_types.cpp b/modules/basis_universal/register_types.cpp index 8e328a519d..4c2ebe603f 100644 --- a/modules/basis_universal/register_types.cpp +++ b/modules/basis_universal/register_types.cpp @@ -143,12 +143,11 @@ static Vector basis_universal_packer(const Ref &p_image, Image:: } #endif // TOOLS_ENABLED -static Ref basis_universal_unpacker(const Vector &p_buffer) { +static Ref basis_universal_unpacker_ptr(const uint8_t *p_data, int p_size) { Ref image; - const uint8_t *r = p_buffer.ptr(); - const uint8_t *ptr = r; - int size = p_buffer.size(); + const uint8_t *ptr = p_data; + int size = p_size; basist::transcoder_texture_format format = basist::transcoder_texture_format::cTFTotalTextureFormats; Image::Format imgfmt = Image::FORMAT_MAX; @@ -259,6 +258,14 @@ static Ref basis_universal_unpacker(const Vector &p_buffer) { return image; } +static Ref basis_universal_unpacker(const Vector &p_buffer) { + Ref image; + + const uint8_t *r = p_buffer.ptr(); + int size = p_buffer.size(); + return basis_universal_unpacker_ptr(r, size); +} + void register_basis_universal_types() { #ifdef TOOLS_ENABLED using namespace basisu; @@ -267,6 +274,7 @@ void register_basis_universal_types() { Image::basis_universal_packer = basis_universal_packer; #endif Image::basis_universal_unpacker = basis_universal_unpacker; + Image::basis_universal_unpacker_ptr = basis_universal_unpacker_ptr; } void unregister_basis_universal_types() { @@ -274,4 +282,5 @@ void unregister_basis_universal_types() { Image::basis_universal_packer = nullptr; #endif Image::basis_universal_unpacker = nullptr; + Image::basis_universal_unpacker_ptr = nullptr; } diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index 52d6f6e63b..032f43d9b9 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -809,6 +809,7 @@ void register_scene_types() { GDREGISTER_VIRTUAL_CLASS(Texture2D); GDREGISTER_CLASS(Sky); GDREGISTER_CLASS(CompressedTexture2D); + GDREGISTER_CLASS(PortableCompressedTexture2D); GDREGISTER_CLASS(ImageTexture); GDREGISTER_CLASS(AtlasTexture); GDREGISTER_CLASS(MeshTexture); diff --git a/scene/resources/texture.cpp b/scene/resources/texture.cpp index 3113987fbc..a66d9d9f27 100644 --- a/scene/resources/texture.cpp +++ b/scene/resources/texture.cpp @@ -32,12 +32,12 @@ #include "core/core_string_names.h" #include "core/io/image_loader.h" +#include "core/io/marshalls.h" #include "core/math/geometry_2d.h" #include "core/os/os.h" #include "mesh.h" #include "scene/resources/bit_map.h" #include "servers/camera/camera_feed.h" - int Texture2D::get_width() const { int ret; if (GDVIRTUAL_REQUIRED_CALL(_get_width, ret)) { @@ -338,6 +338,312 @@ ImageTexture::~ImageTexture() { } } +///////////////////// + +void PortableCompressedTexture2D::_set_data(const Vector &p_data) { + if (p_data.size() == 0) { + return; //nothing to do + } + + const uint8_t *data = p_data.ptr(); + uint32_t data_size = p_data.size(); + ERR_FAIL_COND(data_size < 20); + compression_mode = CompressionMode(decode_uint32(data + 0)); + format = Image::Format(decode_uint32(data + 4)); + uint32_t mipmap_count = decode_uint32(data + 8); + size.width = decode_uint32(data + 12); + size.height = decode_uint32(data + 16); + mipmaps = mipmap_count > 1; + + data += 20; + data_size -= 20; + + Ref image; + + switch (compression_mode) { + case COMPRESSION_MODE_LOSSLESS: + case COMPRESSION_MODE_LOSSY: { + Vector image_data; + + ERR_FAIL_COND(data_size < 4); + for (uint32_t i = 0; i < mipmap_count; i++) { + uint32_t mipsize = decode_uint32(data); + data += 4; + data_size -= 4; + ERR_FAIL_COND(mipsize < data_size); + Ref img = memnew(Image(data, data_size)); + ERR_FAIL_COND(img->is_empty()); + if (img->get_format() != format) { // May happen due to webp/png in the tiny mipmaps. + img->convert(format); + } + image_data.append_array(img->get_data()); + + data += mipsize; + data_size -= mipsize; + } + + image = Ref(memnew(Image(size.width, size.height, mipmap_count > 1, format, image_data))); + + } break; + case COMPRESSION_MODE_BASIS_UNIVERSAL: { + ERR_FAIL_COND(!Image::basis_universal_unpacker_ptr); + image = Image::basis_universal_unpacker_ptr(data, data_size); + + } break; + case COMPRESSION_MODE_S3TC: + case COMPRESSION_MODE_ETC2: + case COMPRESSION_MODE_BPTC: { + image = Ref(memnew(Image(size.width, size.height, mipmap_count > 1, format, p_data.slice(20)))); + } break; + } + ERR_FAIL_COND(image.is_null()); + + if (texture.is_null()) { + texture = RenderingServer::get_singleton()->texture_2d_create(image); + } else { + RID new_texture = RenderingServer::get_singleton()->texture_2d_create(image); + RenderingServer::get_singleton()->texture_replace(texture, new_texture); + } + + image_stored = true; + RenderingServer::get_singleton()->texture_set_size_override(texture, size_override.width, size_override.height); + alpha_cache.unref(); + + if (keep_all_compressed_buffers || keep_compressed_buffer) { + compressed_buffer = p_data; + } else { + compressed_buffer.clear(); + } +} + +PortableCompressedTexture2D::CompressionMode PortableCompressedTexture2D::get_compression_mode() const { + return compression_mode; +} +Vector PortableCompressedTexture2D::_get_data() const { + return compressed_buffer; +} + +void PortableCompressedTexture2D::create_from_image(const Ref &p_image, CompressionMode p_compression_mode, bool p_normal_map, float p_lossy_quality) { + ERR_FAIL_COND(p_image.is_null() || p_image->is_empty()); + + Vector buffer; + + buffer.resize(20); + encode_uint32(p_compression_mode, buffer.ptrw()); + encode_uint32(p_image->get_format(), buffer.ptrw() + 4); + encode_uint32(p_image->get_mipmap_count() + 1, buffer.ptrw() + 8); + encode_uint32(p_image->get_width(), buffer.ptrw() + 12); + encode_uint32(p_image->get_height(), buffer.ptrw() + 16); + + switch (p_compression_mode) { + case COMPRESSION_MODE_LOSSLESS: + case COMPRESSION_MODE_LOSSY: { + for (int i = 0; i < p_image->get_mipmap_count() + 1; i++) { + Vector data; + if (p_compression_mode == COMPRESSION_MODE_LOSSY) { + data = Image::webp_lossy_packer(p_image->get_image_from_mipmap(i), p_lossy_quality); + } else { + data = Image::webp_lossless_packer(p_image->get_image_from_mipmap(i)); + } + int data_len = data.size(); + buffer.resize(buffer.size() + 4); + encode_uint32(data_len, buffer.ptrw() + buffer.size() - 4); + buffer.append_array(data); + } + } break; + case COMPRESSION_MODE_BASIS_UNIVERSAL: { + Image::UsedChannels uc = p_image->detect_used_channels(p_normal_map ? Image::COMPRESS_SOURCE_NORMAL : Image::COMPRESS_SOURCE_GENERIC); + Vector budata = Image::basis_universal_packer(p_image, uc); + buffer.append_array(budata); + + } break; + case COMPRESSION_MODE_S3TC: + case COMPRESSION_MODE_ETC2: + case COMPRESSION_MODE_BPTC: { + Ref copy = p_image->duplicate(); + switch (p_compression_mode) { + case COMPRESSION_MODE_S3TC: + copy->compress(Image::COMPRESS_S3TC); + break; + case COMPRESSION_MODE_ETC2: + copy->compress(Image::COMPRESS_ETC2); + break; + case COMPRESSION_MODE_BPTC: + copy->compress(Image::COMPRESS_BPTC); + break; + default: { + }; + } + + buffer.append_array(copy->get_data()); + + } break; + } + + _set_data(buffer); +} + +Image::Format PortableCompressedTexture2D::get_format() const { + return format; +} + +Ref PortableCompressedTexture2D::get_image() const { + if (image_stored) { + return RenderingServer::get_singleton()->texture_2d_get(texture); + } else { + return Ref(); + } +} + +int PortableCompressedTexture2D::get_width() const { + return size.width; +} + +int PortableCompressedTexture2D::get_height() const { + return size.height; +} + +RID PortableCompressedTexture2D::get_rid() const { + if (texture.is_null()) { + //we are in trouble, create something temporary + texture = RenderingServer::get_singleton()->texture_2d_placeholder_create(); + } + return texture; +} + +bool PortableCompressedTexture2D::has_alpha() const { + return (format == Image::FORMAT_LA8 || format == Image::FORMAT_RGBA8); +} + +void PortableCompressedTexture2D::draw(RID p_canvas_item, const Point2 &p_pos, const Color &p_modulate, bool p_transpose) const { + if (size.width == 0 || size.height == 0) { + return; + } + RenderingServer::get_singleton()->canvas_item_add_texture_rect(p_canvas_item, Rect2(p_pos, size), texture, false, p_modulate, p_transpose); +} + +void PortableCompressedTexture2D::draw_rect(RID p_canvas_item, const Rect2 &p_rect, bool p_tile, const Color &p_modulate, bool p_transpose) const { + if (size.width == 0 || size.height == 0) { + return; + } + RenderingServer::get_singleton()->canvas_item_add_texture_rect(p_canvas_item, p_rect, texture, p_tile, p_modulate, p_transpose); +} + +void PortableCompressedTexture2D::draw_rect_region(RID p_canvas_item, const Rect2 &p_rect, const Rect2 &p_src_rect, const Color &p_modulate, bool p_transpose, bool p_clip_uv) const { + if (size.width == 0 || size.height == 0) { + return; + } + RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas_item, p_rect, texture, p_src_rect, p_modulate, p_transpose, p_clip_uv); +} + +bool PortableCompressedTexture2D::is_pixel_opaque(int p_x, int p_y) const { + if (!alpha_cache.is_valid()) { + Ref img = get_image(); + if (img.is_valid()) { + if (img->is_compressed()) { //must decompress, if compressed + Ref decom = img->duplicate(); + decom->decompress(); + img = decom; + } + alpha_cache.instantiate(); + alpha_cache->create_from_image_alpha(img); + } + } + + if (alpha_cache.is_valid()) { + int aw = int(alpha_cache->get_size().width); + int ah = int(alpha_cache->get_size().height); + if (aw == 0 || ah == 0) { + return true; + } + + int x = p_x * aw / size.width; + int y = p_y * ah / size.height; + + x = CLAMP(x, 0, aw); + y = CLAMP(y, 0, ah); + + return alpha_cache->get_bit(Point2(x, y)); + } + + return true; +} + +void PortableCompressedTexture2D::set_size_override(const Size2 &p_size) { + size_override = p_size; + RenderingServer::get_singleton()->texture_set_size_override(texture, size_override.width, size_override.height); +} + +Size2 PortableCompressedTexture2D::get_size_override() const { + return size_override; +} + +void PortableCompressedTexture2D::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); +} + +bool PortableCompressedTexture2D::keep_all_compressed_buffers = false; + +void PortableCompressedTexture2D::set_keep_all_compressed_buffers(bool p_keep) { + keep_all_compressed_buffers = p_keep; +} + +bool PortableCompressedTexture2D::is_keeping_all_compressed_buffers() { + return keep_all_compressed_buffers; +} + +void PortableCompressedTexture2D::set_keep_compressed_buffer(bool p_keep) { + keep_compressed_buffer = p_keep; + if (!p_keep) { + compressed_buffer.clear(); + } +} + +bool PortableCompressedTexture2D::is_keeping_compressed_buffer() const { + return keep_compressed_buffer; +} + +void PortableCompressedTexture2D::_bind_methods() { + ClassDB::bind_method(D_METHOD("create_from_image", "image", "compression_mode", "normal_map", "lossy_quality"), &PortableCompressedTexture2D::create_from_image, DEFVAL(false), DEFVAL(0.8)); + ClassDB::bind_method(D_METHOD("get_format"), &PortableCompressedTexture2D::get_format); + ClassDB::bind_method(D_METHOD("get_compression_mode"), &PortableCompressedTexture2D::get_compression_mode); + + ClassDB::bind_method(D_METHOD("set_size_override", "size"), &PortableCompressedTexture2D::set_size_override); + ClassDB::bind_method(D_METHOD("get_size_override"), &PortableCompressedTexture2D::get_size_override); + + ClassDB::bind_method(D_METHOD("set_keep_compressed_buffer", "keep"), &PortableCompressedTexture2D::set_keep_compressed_buffer); + ClassDB::bind_method(D_METHOD("is_keeping_compressed_buffer"), &PortableCompressedTexture2D::is_keeping_compressed_buffer); + + ClassDB::bind_method(D_METHOD("_set_data", "data"), &PortableCompressedTexture2D::_set_data); + ClassDB::bind_method(D_METHOD("_get_data"), &PortableCompressedTexture2D::_get_data); + + ClassDB::bind_static_method("PortableCompressedTexture2D", D_METHOD("set_keep_all_compressed_buffers", "keep"), &PortableCompressedTexture2D::set_keep_all_compressed_buffers); + ClassDB::bind_static_method("PortableCompressedTexture2D", D_METHOD("is_keeping_all_compressed_buffers"), &PortableCompressedTexture2D::is_keeping_all_compressed_buffers); + + ADD_PROPERTY(PropertyInfo(Variant::PACKED_BYTE_ARRAY, "_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "_set_data", "_get_data"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "size_override"), "set_size_override", "get_size_override"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "keep_compressed_buffer"), "set_keep_compressed_buffer", "is_keeping_compressed_buffer"); + + BIND_ENUM_CONSTANT(COMPRESSION_MODE_LOSSLESS); + BIND_ENUM_CONSTANT(COMPRESSION_MODE_LOSSY); + BIND_ENUM_CONSTANT(COMPRESSION_MODE_BASIS_UNIVERSAL); + BIND_ENUM_CONSTANT(COMPRESSION_MODE_S3TC); + BIND_ENUM_CONSTANT(COMPRESSION_MODE_ETC2); + BIND_ENUM_CONSTANT(COMPRESSION_MODE_BPTC); +} + +PortableCompressedTexture2D::PortableCompressedTexture2D() {} + +PortableCompressedTexture2D::~PortableCompressedTexture2D() { + if (texture.is_valid()) { + RenderingServer::get_singleton()->free(texture); + } +} + ////////////////////////////////////////// Ref CompressedTexture2D::load_image_from_file(FileAccess *f, int p_size_limit) { diff --git a/scene/resources/texture.h b/scene/resources/texture.h index 1e07b83547..7e194fd21d 100644 --- a/scene/resources/texture.h +++ b/scene/resources/texture.h @@ -137,6 +137,78 @@ public: ~ImageTexture(); }; +class PortableCompressedTexture2D : public Texture2D { + GDCLASS(PortableCompressedTexture2D, Texture2D); + +public: + enum CompressionMode { + COMPRESSION_MODE_LOSSLESS, + COMPRESSION_MODE_LOSSY, + COMPRESSION_MODE_BASIS_UNIVERSAL, + COMPRESSION_MODE_S3TC, + COMPRESSION_MODE_ETC2, + COMPRESSION_MODE_BPTC, + }; + +private: + CompressionMode compression_mode = COMPRESSION_MODE_LOSSLESS; + static bool keep_all_compressed_buffers; + bool keep_compressed_buffer = false; + Vector compressed_buffer; + Size2 size; + Size2 size_override; + bool mipmaps = false; + Image::Format format = Image::FORMAT_L8; + + mutable RID texture; + mutable Ref alpha_cache; + + bool image_stored = false; + +protected: + Vector _get_data() const; + void _set_data(const Vector &p_data); + + static void _bind_methods(); + +public: + CompressionMode get_compression_mode() const; + void create_from_image(const Ref &p_image, CompressionMode p_compression_mode, bool p_normal_map = false, float p_lossy_quality = 0.8); + + Image::Format get_format() const; + + void update(const Ref &p_image); + Ref get_image() const override; + + int get_width() const override; + int get_height() const override; + + virtual RID get_rid() const override; + + bool has_alpha() const override; + virtual void draw(RID p_canvas_item, const Point2 &p_pos, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false) const override; + virtual void draw_rect(RID p_canvas_item, const Rect2 &p_rect, bool p_tile = false, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false) const override; + virtual void draw_rect_region(RID p_canvas_item, const Rect2 &p_rect, const Rect2 &p_src_rect, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false, bool p_clip_uv = true) const override; + + bool is_pixel_opaque(int p_x, int p_y) const override; + + virtual void set_path(const String &p_path, bool p_take_over = false) override; + + void set_size_override(const Size2 &p_size); + Size2 get_size_override() const; + + void set_keep_compressed_buffer(bool p_keep); + bool is_keeping_compressed_buffer() const; + + static void set_keep_all_compressed_buffers(bool p_keep); + static bool is_keeping_all_compressed_buffers(); + + PortableCompressedTexture2D(); + ~PortableCompressedTexture2D(); +}; + +VARIANT_ENUM_CAST(PortableCompressedTexture2D::CompressionMode) + class CompressedTexture2D : public Texture2D { GDCLASS(CompressedTexture2D, Texture2D); -- cgit v1.2.3