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.
This commit is contained in:
parent
f6ef63635f
commit
45f74ceb85
9 changed files with 482 additions and 8 deletions
|
|
@ -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<uint8_t> &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> image;
|
||||
|
||||
switch (compression_mode) {
|
||||
case COMPRESSION_MODE_LOSSLESS:
|
||||
case COMPRESSION_MODE_LOSSY: {
|
||||
Vector<uint8_t> 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<Image> 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<Image>(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<Image>(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<uint8_t> PortableCompressedTexture2D::_get_data() const {
|
||||
return compressed_buffer;
|
||||
}
|
||||
|
||||
void PortableCompressedTexture2D::create_from_image(const Ref<Image> &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<uint8_t> 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<uint8_t> 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<uint8_t> 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<Image> 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<Image> PortableCompressedTexture2D::get_image() const {
|
||||
if (image_stored) {
|
||||
return RenderingServer::get_singleton()->texture_2d_get(texture);
|
||||
} else {
|
||||
return Ref<Image>();
|
||||
}
|
||||
}
|
||||
|
||||
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<Image> img = get_image();
|
||||
if (img.is_valid()) {
|
||||
if (img->is_compressed()) { //must decompress, if compressed
|
||||
Ref<Image> 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<Image> CompressedTexture2D::load_image_from_file(FileAccess *f, int p_size_limit) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue