Skip to content

Commit

Permalink
Update filled image generation for compressed formats
Browse files Browse the repository at this point in the history
  • Loading branch information
TokisanGames committed Jan 7, 2024
1 parent d1cd0de commit ac4d420
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 57 deletions.
2 changes: 1 addition & 1 deletion src/terrain_3d.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1049,7 +1049,7 @@ void Terrain3D::_bind_methods() {
// Utility functions
ClassDB::bind_static_method("Terrain3D", D_METHOD("get_min_max", "image"), &Util::get_min_max);
ClassDB::bind_static_method("Terrain3D", D_METHOD("get_thumbnail", "image", "size"), &Util::get_thumbnail, DEFVAL(Vector2i(256, 256)));
ClassDB::bind_static_method("Terrain3D", D_METHOD("get_filled_image", "size", "color", "create_mipmaps", "format"), &Util::get_filled_image); //, DEFVAL(Vector2i(256, 256)));
ClassDB::bind_static_method("Terrain3D", D_METHOD("get_filled_image", "size", "color", "create_mipmaps", "format"), &Util::get_filled_image);

// Expose 'update_aabbs' so it can be used in Callable. Not ideal.
ClassDB::bind_method(D_METHOD("update_aabbs"), &Terrain3D::update_aabbs);
Expand Down
2 changes: 1 addition & 1 deletion src/terrain_3d_texture.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ bool Terrain3DTexture::_is_texture_valid(const Ref<Texture2D> &p_texture) const
format = img->get_format();
}
if (format < 0 || format >= Image::FORMAT_MAX) {
LOG(ERROR, "Expects an actual texture file. See documentation for format specification.");
LOG(ERROR, "Invalid texture format. See documentation for format specification.");
return false;
}

Expand Down
63 changes: 36 additions & 27 deletions src/terrain_3d_texture_list.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,44 +56,55 @@ void Terrain3DTextureList::_update_texture_data(bool p_textures, bool p_settings
Vector2i albedo_size = Vector2i(0, 0);
Vector2i normal_size = Vector2i(0, 0);

Image::Format alb_format = Image::FORMAT_MAX;
Image::Format nor_format = Image::FORMAT_MAX;
Image::Format albedo_format = Image::FORMAT_MAX;
Image::Format normal_format = Image::FORMAT_MAX;
bool albedo_mipmaps = true;
bool normal_mipmaps = true;

// Detect image sizes
// Detect image sizes and formats
for (int i = 0; i < _textures.size(); i++) {
Ref<Terrain3DTexture> texture_set = _textures[i];
if (texture_set.is_null()) {
continue;
}
Ref<Texture2D> alb_tex = texture_set->get_albedo_texture();
Ref<Texture2D> nor_tex = texture_set->get_normal_texture();
Ref<Texture2D> albedo_tex = texture_set->get_albedo_texture();
Ref<Texture2D> normal_tex = texture_set->get_normal_texture();

if (alb_tex.is_valid()) {
Vector2i tex_size = alb_tex->get_size();
// If this is the first texture, set expected size and format for the arrays
if (albedo_tex.is_valid()) {
Vector2i tex_size = albedo_tex->get_size();
if (albedo_size.length() == 0.0) {
albedo_size = tex_size;
} else {
ERR_FAIL_COND_MSG(tex_size != albedo_size, "Albedo textures do not have same size!");
} else if (tex_size != albedo_size) {
LOG(ERROR, "Texture ID ", i, " albedo size: ", tex_size, " doesn't match first texture: ", albedo_size);
return;
}
Image::Format format = alb_tex->get_image()->get_format();
if (alb_format == Image::FORMAT_MAX) {
alb_format = format;
} else {
ERR_FAIL_COND_MSG(alb_format != format, "Albedo textures do not have same format! See documentation for format specification.");
Ref<Image> img = albedo_tex->get_image();
Image::Format format = img->get_format();
if (albedo_format == Image::FORMAT_MAX) {
albedo_format = format;
albedo_mipmaps = img->has_mipmaps();
} else if (format != albedo_format) {
LOG(ERROR, "Texture ID ", i, " albedo format: ", format, " doesn't match first texture: ", albedo_format);
return;
}
}
if (nor_tex.is_valid()) {
Vector2i tex_size = nor_tex->get_size();
if (normal_tex.is_valid()) {
Vector2i tex_size = normal_tex->get_size();
if (normal_size.length() == 0.0) {
normal_size = tex_size;
} else {
ERR_FAIL_COND_MSG(tex_size != normal_size, "Normal map textures do not have same size!");
} else if (tex_size != normal_size) {
LOG(ERROR, "Texture ID ", i, " normal size: ", tex_size, " doesn't match first texture: ", normal_size);
return;
}
Image::Format format = nor_tex->get_image()->get_format();
if (nor_format == Image::FORMAT_MAX) {
nor_format = format;
} else {
ERR_FAIL_COND_MSG(nor_format != format, "Normal map textures do not have same format! See documentation for format specification.");
Ref<Image> img = normal_tex->get_image();
Image::Format format = img->get_format();
if (normal_format == Image::FORMAT_MAX) {
normal_format = format;
normal_mipmaps = img->has_mipmaps();
} else if (format != normal_format) {
LOG(ERROR, "Texture ID ", i, " normal format: ", format, " doesn't match first texture: ", normal_format);
return;
}
}
}
Expand Down Expand Up @@ -122,8 +133,7 @@ void Terrain3DTextureList::_update_texture_data(bool p_textures, bool p_settings
Ref<Image> img;

if (tex.is_null()) {
img = Util::get_filled_image(albedo_size, COLOR_CHECKED, true,
alb_format == Image::FORMAT_MAX ? Image::FORMAT_DXT5 : alb_format);
img = Util::get_filled_image(albedo_size, COLOR_CHECKED, albedo_mipmaps, albedo_format);
LOG(DEBUG, "ID ", i, " albedo texture is null. Creating a new one. Format: ", img->get_format());
texture_set->get_data()->_albedo_texture = ImageTexture::create_from_image(img);
} else {
Expand Down Expand Up @@ -152,8 +162,7 @@ void Terrain3DTextureList::_update_texture_data(bool p_textures, bool p_settings
Ref<Image> img;

if (tex.is_null()) {
img = Util::get_filled_image(normal_size, COLOR_NORMAL, true,
nor_format == Image::FORMAT_MAX ? Image::FORMAT_DXT5 : nor_format);
img = Util::get_filled_image(normal_size, COLOR_NORMAL, normal_mipmaps, normal_format);
LOG(DEBUG, "ID ", i, " normal texture is null. Creating a new one. Format: ", img->get_format());
texture_set->get_data()->_normal_texture = ImageTexture::create_from_image(img);
} else {
Expand Down
91 changes: 64 additions & 27 deletions src/util.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
// Copyright © 2023 Cory Petkovsek, Roope Palmroos, and Contributors.

#include "util.h"
#include <godot_cpp/classes/engine.hpp>

#include "logger.h"
#include "util.h"

///////////////////////////
// Public Functions
Expand Down Expand Up @@ -103,39 +105,74 @@ Ref<Image> Util::get_thumbnail(const Ref<Image> p_image, Vector2i p_size) {
return thumb;
}

/* Get image filled with your desired color and format
* If alpha < 0, fill with checkered pattern multiplied by rgb
/* Get an Image filled with specified color and format
* If p_color.a < 0, fill with checkered pattern multiplied by p_color.rgb
*
* Behavior changes if a compressed format is requested:
* If the editor is running and format is DXT1/5, BPTC_RGBA, it returns a filled image.
* Otherwise, it returns a blank image in that format.
*
* The reason is the Image compression library is available only in the editor. And it is
* unreliable, offering little control over the output format, choosing automatically and
* often wrong. We have selected a few compressed formats it gets right.
*/
Ref<Image> Util::get_filled_image(Vector2i p_size, Color p_color, bool p_create_mipmaps, Image::Format p_format) {
if (p_format < 0 || p_format >= Image::FORMAT_MAX) {
p_format = Image::FORMAT_DXT5;
}

Image::CompressMode compression_format = Image::COMPRESS_MAX;
Image::UsedChannels channels = Image::USED_CHANNELS_RGBA;
bool compress = false;
bool fill_image = true;

if (p_format >= Image::Format::FORMAT_DXT1) {
// Compressed formats.
if (p_format == Image::Format::FORMAT_DXT5) {
Ref<Image> img = Util::get_filled_image(p_size, p_color, p_create_mipmaps, Image::Format::FORMAT_RGBA8);
img->compress_from_channels(Image::COMPRESS_S3TC, Image::USED_CHANNELS_RGBA);
return img;
switch (p_format) {
case Image::FORMAT_DXT1:
p_format = Image::FORMAT_RGB8;
channels = Image::USED_CHANNELS_RGB;
compression_format = Image::COMPRESS_S3TC;
compress = true;
break;
case Image::FORMAT_DXT5:
p_format = Image::FORMAT_RGBA8;
channels = Image::USED_CHANNELS_RGBA;
compression_format = Image::COMPRESS_S3TC;
compress = true;
break;
case Image::FORMAT_BPTC_RGBA:
p_format = Image::FORMAT_RGBA8;
channels = Image::USED_CHANNELS_RGBA;
compression_format = Image::COMPRESS_BPTC;
compress = true;
break;
default:
compress = false;
fill_image = false;
break;
}
if (p_format == Image::Format::FORMAT_BPTC_RGBA) {
Ref<Image> img = Util::get_filled_image(p_size, p_color, p_create_mipmaps, Image::Format::FORMAT_RGBA8);
img->compress_from_channels(Image::COMPRESS_BPTC, Image::USED_CHANNELS_RGBA);
return img;
}
// For other compressed formats, just return a blank image.
return Image::create(p_size.x, p_size.y, p_create_mipmaps, p_format);
}

Ref<Image> img = Image::create(p_size.x, p_size.y, p_create_mipmaps, p_format);
if (p_color.a < 0.0f) {
p_color.a = 1.0f;
Color col_a = Color(0.8f, 0.8f, 0.8f, 1.0) * p_color;
Color col_b = Color(0.5f, 0.5f, 0.5f, 1.0) * p_color;
img->fill_rect(Rect2i(Vector2i(0, 0), p_size / 2), col_a);
img->fill_rect(Rect2i(p_size / 2, p_size / 2), col_a);
img->fill_rect(Rect2i(Vector2(p_size.x, 0) / 2, p_size / 2), col_b);
img->fill_rect(Rect2i(Vector2(0, p_size.y) / 2, p_size / 2), col_b);
} else {
img->fill(p_color);

if (fill_image) {
if (p_color.a < 0.0f) {
p_color.a = 1.0f;
Color col_a = Color(0.8f, 0.8f, 0.8f, 1.0) * p_color;
Color col_b = Color(0.5f, 0.5f, 0.5f, 1.0) * p_color;
img->fill_rect(Rect2i(Vector2i(0, 0), p_size / 2), col_a);
img->fill_rect(Rect2i(p_size / 2, p_size / 2), col_a);
img->fill_rect(Rect2i(Vector2(p_size.x, 0) / 2, p_size / 2), col_b);
img->fill_rect(Rect2i(Vector2(0, p_size.y) / 2, p_size / 2), col_b);
} else {
img->fill(p_color);
}
if (p_create_mipmaps) {
img->generate_mipmaps();
}
}
if (p_create_mipmaps) {
img->generate_mipmaps();
if (compress && Engine::get_singleton()->is_editor_hint()) {
img->compress_from_channels(compression_format, channels);
}
return img;
}
2 changes: 1 addition & 1 deletion src/util.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ class Util {
static Ref<Image> get_filled_image(Vector2i p_size,
Color p_color = COLOR_BLACK,
bool p_create_mipmaps = true,
Image::Format p_format = Image::FORMAT_RF);
Image::Format p_format = Image::FORMAT_MAX);
};

#endif // UTIL_CLASS_H

0 comments on commit ac4d420

Please sign in to comment.