Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement lossless WebP encoding #47835

Merged
merged 1 commit into from
Jun 11, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions core/io/image.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2718,10 +2718,11 @@ void (*Image::_image_decompress_bptc)(Image *) = nullptr;
void (*Image::_image_decompress_etc1)(Image *) = nullptr;
void (*Image::_image_decompress_etc2)(Image *) = nullptr;

Vector<uint8_t> (*Image::lossy_packer)(const Ref<Image> &, float) = nullptr;
Ref<Image> (*Image::lossy_unpacker)(const Vector<uint8_t> &) = nullptr;
Vector<uint8_t> (*Image::lossless_packer)(const Ref<Image> &) = nullptr;
Ref<Image> (*Image::lossless_unpacker)(const Vector<uint8_t> &) = nullptr;
Vector<uint8_t> (*Image::webp_lossy_packer)(const Ref<Image> &, float) = nullptr;
Vector<uint8_t> (*Image::webp_lossless_packer)(const Ref<Image> &) = nullptr;
Ref<Image> (*Image::webp_unpacker)(const Vector<uint8_t> &) = nullptr;
Vector<uint8_t> (*Image::png_packer)(const Ref<Image> &) = nullptr;
Ref<Image> (*Image::png_unpacker)(const Vector<uint8_t> &) = nullptr;
Vector<uint8_t> (*Image::basis_universal_packer)(const Ref<Image> &, Image::UsedChannels) = nullptr;
Ref<Image> (*Image::basis_universal_unpacker)(const Vector<uint8_t> &) = nullptr;

Expand Down
9 changes: 5 additions & 4 deletions core/io/image.h
Original file line number Diff line number Diff line change
Expand Up @@ -148,10 +148,11 @@ class Image : public Resource {
static void (*_image_decompress_etc1)(Image *);
static void (*_image_decompress_etc2)(Image *);

static Vector<uint8_t> (*lossy_packer)(const Ref<Image> &p_image, float p_quality);
static Ref<Image> (*lossy_unpacker)(const Vector<uint8_t> &p_buffer);
static Vector<uint8_t> (*lossless_packer)(const Ref<Image> &p_image);
static Ref<Image> (*lossless_unpacker)(const Vector<uint8_t> &p_buffer);
static Vector<uint8_t> (*webp_lossy_packer)(const Ref<Image> &p_image, float p_quality);
static Vector<uint8_t> (*webp_lossless_packer)(const Ref<Image> &p_image);
static Ref<Image> (*webp_unpacker)(const Vector<uint8_t> &p_buffer);
static Vector<uint8_t> (*png_packer)(const Ref<Image> &p_image);
static Ref<Image> (*png_unpacker)(const Vector<uint8_t> &p_buffer);
static Vector<uint8_t> (*basis_universal_packer)(const Ref<Image> &p_image, UsedChannels p_channels);
static Ref<Image> (*basis_universal_unpacker)(const Vector<uint8_t> &p_buffer);

Expand Down
6 changes: 6 additions & 0 deletions doc/classes/ProjectSettings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1578,6 +1578,12 @@
<member name="rendering/textures/default_filters/use_nearest_mipmap_filter" type="bool" setter="" getter="" default="false">
If [code]true[/code], uses nearest-neighbor mipmap filtering when using mipmaps (also called "bilinear filtering"), which will result in visible seams appearing between mipmap stages. This may increase performance in mobile as less memory bandwidth is used. If [code]false[/code], linear mipmap filtering (also called "trilinear filtering") is used.
</member>
<member name="rendering/textures/lossless_compression/force_png" type="bool" setter="" getter="" default="false">
If [code]true[/code], the texture importer will import lossless textures using the PNG format. Otherwise, it will default to using WebP.
</member>
<member name="rendering/textures/lossless_compression/webp_compression_level" type="int" setter="" getter="" default="2">
The default compression level for lossless WebP. Higher levels result in smaller files at the cost of compression speed. Decompression speed is mostly unaffected by the compression level. Supported values are 0 to 9. Note that compression levels above 6 are very slow and offer very little savings.
</member>
<member name="rendering/textures/vram_compression/import_bptc" type="bool" setter="" getter="" default="false">
If [code]true[/code], the texture importer will import VRAM-compressed textures using the BPTC algorithm. This texture compression algorithm is only supported on desktop platforms, and only when using the Vulkan renderer.
</member>
Expand Down
4 changes: 2 additions & 2 deletions drivers/png/image_loader_png.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,6 @@ Vector<uint8_t> ImageLoaderPNG::lossless_pack_png(const Ref<Image> &p_image) {

ImageLoaderPNG::ImageLoaderPNG() {
Image::_png_mem_loader_func = load_mem_png;
Image::lossless_unpacker = lossless_unpack_png;
Image::lossless_packer = lossless_pack_png;
Image::png_unpacker = lossless_unpack_png;
Image::png_packer = lossless_pack_png;
}
15 changes: 11 additions & 4 deletions editor/import/resource_importer_texture.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -218,14 +218,21 @@ void ResourceImporterTexture::get_import_options(List<ImportOption> *r_options,
void ResourceImporterTexture::save_to_stex_format(FileAccess *f, const Ref<Image> &p_image, CompressMode p_compress_mode, Image::UsedChannels p_channels, Image::CompressMode p_compress_format, float p_lossy_quality) {
switch (p_compress_mode) {
case COMPRESS_LOSSLESS: {
f->store_32(StreamTexture2D::DATA_FORMAT_LOSSLESS);
bool lossless_force_png = ProjectSettings::get_singleton()->get("rendering/textures/lossless_compression/force_png");
bool use_webp = !lossless_force_png && p_image->get_width() <= 16383 && p_image->get_height() <= 16383; // WebP has a size limit
f->store_32(use_webp ? StreamTexture2D::DATA_FORMAT_WEBP : StreamTexture2D::DATA_FORMAT_PNG);
f->store_16(p_image->get_width());
f->store_16(p_image->get_height());
f->store_32(p_image->get_mipmap_count());
f->store_32(p_image->get_format());

for (int i = 0; i < p_image->get_mipmap_count() + 1; i++) {
Vector<uint8_t> data = Image::lossless_packer(p_image->get_image_from_mipmap(i));
Vector<uint8_t> data;
if (use_webp) {
data = Image::webp_lossless_packer(p_image->get_image_from_mipmap(i));
} else {
data = Image::png_packer(p_image->get_image_from_mipmap(i));
}
int data_len = data.size();
f->store_32(data_len);

Expand All @@ -235,14 +242,14 @@ void ResourceImporterTexture::save_to_stex_format(FileAccess *f, const Ref<Image

} break;
case COMPRESS_LOSSY: {
f->store_32(StreamTexture2D::DATA_FORMAT_LOSSY);
f->store_32(StreamTexture2D::DATA_FORMAT_WEBP);
f->store_16(p_image->get_width());
f->store_16(p_image->get_height());
f->store_32(p_image->get_mipmap_count());
f->store_32(p_image->get_format());

for (int i = 0; i < p_image->get_mipmap_count() + 1; i++) {
Vector<uint8_t> data = Image::lossy_packer(p_image->get_image_from_mipmap(i), p_lossy_quality);
Vector<uint8_t> data = Image::webp_lossy_packer(p_image->get_image_from_mipmap(i), p_lossy_quality);
int data_len = data.size();
f->store_32(data_len);

Expand Down
75 changes: 71 additions & 4 deletions modules/webp/image_loader_webp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@

#include "image_loader_webp.h"

#include "core/config/project_settings.h"
#include "core/io/marshalls.h"
#include "core/os/os.h"
#include "core/string/print_string.h"
Expand Down Expand Up @@ -69,12 +70,77 @@ static Vector<uint8_t> _webp_lossy_pack(const Ref<Image> &p_image, float p_quali
w[2] = 'B';
w[3] = 'P';
memcpy(&w[4], dst_buff, dst_size);
free(dst_buff);
WebPFree(dst_buff);

return dst;
}

static Ref<Image> _webp_lossy_unpack(const Vector<uint8_t> &p_buffer) {
static Vector<uint8_t> _webp_lossless_pack(const Ref<Image> &p_image) {
ERR_FAIL_COND_V(p_image.is_null() || p_image->is_empty(), Vector<uint8_t>());

int compression_level = ProjectSettings::get_singleton()->get("rendering/textures/lossless_compression/webp_compression_level");
compression_level = CLAMP(compression_level, 0, 9);

Ref<Image> img = p_image->duplicate();
if (img->detect_alpha()) {
img->convert(Image::FORMAT_RGBA8);
} else {
img->convert(Image::FORMAT_RGB8);
}

Size2 s(img->get_width(), img->get_height());
Vector<uint8_t> data = img->get_data();
const uint8_t *r = data.ptr();

// we need to use the more complex API in order to access the 'exact' flag...

WebPConfig config;
mortarroad marked this conversation as resolved.
Show resolved Hide resolved
WebPPicture pic;
if (!WebPConfigInit(&config) || !WebPConfigLosslessPreset(&config, compression_level) || !WebPPictureInit(&pic)) {
ERR_FAIL_V(Vector<uint8_t>());
}

WebPMemoryWriter wrt;
config.exact = 1;
pic.use_argb = 1;
pic.width = s.width;
pic.height = s.height;
pic.writer = WebPMemoryWrite;
pic.custom_ptr = &wrt;
WebPMemoryWriterInit(&wrt);

bool success_import = false;
if (img->get_format() == Image::FORMAT_RGB8) {
success_import = WebPPictureImportRGB(&pic, r, 3 * s.width);
} else {
success_import = WebPPictureImportRGBA(&pic, r, 4 * s.width);
}
bool success_encode = false;
if (success_import) {
success_encode = WebPEncode(&config, &pic);
}
WebPPictureFree(&pic);

if (!success_encode) {
WebPMemoryWriterClear(&wrt);
ERR_FAIL_V_MSG(Vector<uint8_t>(), "WebP packing failed.");
}

// copy from wrt
Vector<uint8_t> dst;
dst.resize(4 + wrt.size);
uint8_t *w = dst.ptrw();
w[0] = 'W';
w[1] = 'E';
w[2] = 'B';
w[3] = 'P';
memcpy(&w[4], wrt.mem, wrt.size);
WebPMemoryWriterClear(&wrt);

return dst;
}

static Ref<Image> _webp_unpack(const Vector<uint8_t> &p_buffer) {
int size = p_buffer.size() - 4;
ERR_FAIL_COND_V(size <= 0, Ref<Image>());
const uint8_t *r = p_buffer.ptr();
Expand Down Expand Up @@ -168,6 +234,7 @@ void ImageLoaderWEBP::get_recognized_extensions(List<String> *p_extensions) cons

ImageLoaderWEBP::ImageLoaderWEBP() {
Image::_webp_mem_loader_func = _webp_mem_loader_func;
Image::lossy_packer = _webp_lossy_pack;
Image::lossy_unpacker = _webp_lossy_unpack;
Image::webp_lossy_packer = _webp_lossy_pack;
Image::webp_lossless_packer = _webp_lossless_pack;
Image::webp_unpacker = _webp_unpack;
}
8 changes: 4 additions & 4 deletions scene/resources/texture.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,7 @@ Ref<Image> StreamTexture2D::load_image_from_file(FileAccess *f, int p_size_limit
uint32_t mipmaps = f->get_32();
Image::Format format = Image::Format(f->get_32());

if (data_format == DATA_FORMAT_LOSSLESS || data_format == DATA_FORMAT_LOSSY || data_format == DATA_FORMAT_BASIS_UNIVERSAL) {
if (data_format == DATA_FORMAT_PNG || data_format == DATA_FORMAT_WEBP || data_format == DATA_FORMAT_BASIS_UNIVERSAL) {
//look for a PNG or WEBP file inside

int sw = w;
Expand Down Expand Up @@ -360,10 +360,10 @@ Ref<Image> StreamTexture2D::load_image_from_file(FileAccess *f, int p_size_limit
Ref<Image> img;
if (data_format == DATA_FORMAT_BASIS_UNIVERSAL) {
img = Image::basis_universal_unpacker(pv);
} else if (data_format == DATA_FORMAT_LOSSLESS) {
img = Image::lossless_unpacker(pv);
} else if (data_format == DATA_FORMAT_PNG) {
img = Image::png_unpacker(pv);
} else {
img = Image::lossy_unpacker(pv);
img = Image::webp_unpacker(pv);
}

if (img.is_null() || img->is_empty()) {
Expand Down
21 changes: 6 additions & 15 deletions scene/resources/texture.h
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,8 @@ class StreamTexture2D : public Texture2D {
public:
enum DataFormat {
DATA_FORMAT_IMAGE,
DATA_FORMAT_LOSSLESS,
DATA_FORMAT_LOSSY,
DATA_FORMAT_PNG,
DATA_FORMAT_WEBP,
DATA_FORMAT_BASIS_UNIVERSAL,
};

Expand All @@ -146,9 +146,6 @@ class StreamTexture2D : public Texture2D {
};

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,
FORMAT_BIT_DETECT_3D = 1 << 24,
Expand Down Expand Up @@ -389,8 +386,8 @@ class StreamTextureLayered : public TextureLayered {
public:
enum DataFormat {
DATA_FORMAT_IMAGE,
DATA_FORMAT_LOSSLESS,
DATA_FORMAT_LOSSY,
DATA_FORMAT_PNG,
DATA_FORMAT_WEBP,
DATA_FORMAT_BASIS_UNIVERSAL,
};

Expand All @@ -399,9 +396,6 @@ class StreamTextureLayered : public TextureLayered {
};

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,
};
Expand Down Expand Up @@ -532,8 +526,8 @@ class StreamTexture3D : public Texture3D {
public:
enum DataFormat {
DATA_FORMAT_IMAGE,
DATA_FORMAT_LOSSLESS,
DATA_FORMAT_LOSSY,
DATA_FORMAT_PNG,
DATA_FORMAT_WEBP,
DATA_FORMAT_BASIS_UNIVERSAL,
};

Expand All @@ -542,9 +536,6 @@ class StreamTexture3D : public Texture3D {
};

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,
};
Expand Down
4 changes: 4 additions & 0 deletions servers/rendering_server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2282,6 +2282,10 @@ RenderingServer::RenderingServer() {
GLOBAL_DEF_RST("rendering/textures/vram_compression/import_etc2", true);
GLOBAL_DEF_RST("rendering/textures/vram_compression/import_pvrtc", false);

GLOBAL_DEF("rendering/textures/lossless_compression/force_png", false);
GLOBAL_DEF("rendering/textures/lossless_compression/webp_compression_level", 2);
ProjectSettings::get_singleton()->set_custom_property_info("rendering/textures/lossless_compression/webp_compression_level", PropertyInfo(Variant::INT, "rendering/textures/lossless_compression/webp_compression_level", PROPERTY_HINT_RANGE, "0,9,1"));

GLOBAL_DEF("rendering/limits/time/time_rollover_secs", 3600);
ProjectSettings::get_singleton()->set_custom_property_info("rendering/limits/time/time_rollover_secs", PropertyInfo(Variant::FLOAT, "rendering/limits/time/time_rollover_secs", PROPERTY_HINT_RANGE, "0,10000,1,or_greater"));

Expand Down