Fix Image CowData crash when baking large lightmaps

This switches to 64-bit integers in select locations of the Image
class, so that image resolutions of 16384×16384 (used by
lightmap texture arrays) can be used properly. Values that are larger
should also work.

VRAM compression is also supported, although most VRAM-compressed
formats are limited to individual slices of 16384×16384. WebP
is limited to 16383×16383 due to format limitations.
This commit is contained in:
Hugo Locurcio 2024-07-12 01:52:55 +02:00 committed by Rémi Verschelde
parent 293c0f7646
commit 0445ccf428
No known key found for this signature in database
GPG Key ID: C3336907360768E1
7 changed files with 70 additions and 76 deletions

View File

@ -300,10 +300,10 @@ int Image::get_format_block_size(Format p_format) {
return 1; return 1;
} }
void Image::_get_mipmap_offset_and_size(int p_mipmap, int &r_offset, int &r_width, int &r_height) const { void Image::_get_mipmap_offset_and_size(int p_mipmap, int64_t &r_offset, int &r_width, int &r_height) const {
int w = width; int w = width;
int h = height; int h = height;
int ofs = 0; int64_t ofs = 0;
int pixel_size = get_format_pixel_size(format); int pixel_size = get_format_pixel_size(format);
int pixel_rshift = get_format_pixel_rshift(format); int pixel_rshift = get_format_pixel_rshift(format);
@ -315,7 +315,7 @@ void Image::_get_mipmap_offset_and_size(int p_mipmap, int &r_offset, int &r_widt
int bw = w % block != 0 ? w + (block - w % block) : w; int bw = w % block != 0 ? w + (block - w % block) : w;
int bh = h % block != 0 ? h + (block - h % block) : h; int bh = h % block != 0 ? h + (block - h % block) : h;
int s = bw * bh; int64_t s = bw * bh;
s *= pixel_size; s *= pixel_size;
s >>= pixel_rshift; s >>= pixel_rshift;
@ -329,37 +329,30 @@ void Image::_get_mipmap_offset_and_size(int p_mipmap, int &r_offset, int &r_widt
r_height = h; r_height = h;
} }
int Image::get_mipmap_offset(int p_mipmap) const { int64_t Image::get_mipmap_offset(int p_mipmap) const {
ERR_FAIL_INDEX_V(p_mipmap, get_mipmap_count() + 1, -1); ERR_FAIL_INDEX_V(p_mipmap, get_mipmap_count() + 1, -1);
int ofs, w, h; int64_t ofs;
int w, h;
_get_mipmap_offset_and_size(p_mipmap, ofs, w, h); _get_mipmap_offset_and_size(p_mipmap, ofs, w, h);
return ofs; return ofs;
} }
int Image::get_mipmap_byte_size(int p_mipmap) const { void Image::get_mipmap_offset_and_size(int p_mipmap, int64_t &r_ofs, int64_t &r_size) const {
ERR_FAIL_INDEX_V(p_mipmap, get_mipmap_count() + 1, -1); int64_t ofs;
int w, h;
int ofs, w, h;
_get_mipmap_offset_and_size(p_mipmap, ofs, w, h); _get_mipmap_offset_and_size(p_mipmap, ofs, w, h);
int ofs2; int64_t ofs2;
_get_mipmap_offset_and_size(p_mipmap + 1, ofs2, w, h);
return ofs2 - ofs;
}
void Image::get_mipmap_offset_and_size(int p_mipmap, int &r_ofs, int &r_size) const {
int ofs, w, h;
_get_mipmap_offset_and_size(p_mipmap, ofs, w, h);
int ofs2;
_get_mipmap_offset_and_size(p_mipmap + 1, ofs2, w, h); _get_mipmap_offset_and_size(p_mipmap + 1, ofs2, w, h);
r_ofs = ofs; r_ofs = ofs;
r_size = ofs2 - ofs; r_size = ofs2 - ofs;
} }
void Image::get_mipmap_offset_size_and_dimensions(int p_mipmap, int &r_ofs, int &r_size, int &w, int &h) const { void Image::get_mipmap_offset_size_and_dimensions(int p_mipmap, int64_t &r_ofs, int64_t &r_size, int &w, int &h) const {
int ofs; int64_t ofs;
_get_mipmap_offset_and_size(p_mipmap, ofs, w, h); _get_mipmap_offset_and_size(p_mipmap, ofs, w, h);
int ofs2, w2, h2; int64_t ofs2;
int w2, h2;
_get_mipmap_offset_and_size(p_mipmap + 1, ofs2, w2, h2); _get_mipmap_offset_and_size(p_mipmap + 1, ofs2, w2, h2);
r_ofs = ofs; r_ofs = ofs;
r_size = ofs2 - ofs; r_size = ofs2 - ofs;
@ -538,8 +531,8 @@ void Image::convert(Format p_new_format) {
} }
} }
int mip_offset = 0; int64_t mip_offset = 0;
int mip_size = 0; int64_t mip_size = 0;
new_img.get_mipmap_offset_and_size(mip, mip_offset, mip_size); new_img.get_mipmap_offset_and_size(mip, mip_offset, mip_size);
memcpy(new_img.data.ptrw() + mip_offset, new_mip->data.ptr(), mip_size); memcpy(new_img.data.ptrw() + mip_offset, new_mip->data.ptr(), mip_size);
@ -555,8 +548,8 @@ void Image::convert(Format p_new_format) {
int conversion_type = format | p_new_format << 8; int conversion_type = format | p_new_format << 8;
for (int mip = 0; mip < mipmap_count; mip++) { for (int mip = 0; mip < mipmap_count; mip++) {
int mip_offset = 0; int64_t mip_offset = 0;
int mip_size = 0; int64_t mip_size = 0;
int mip_width = 0; int mip_width = 0;
int mip_height = 0; int mip_height = 0;
get_mipmap_offset_size_and_dimensions(mip, mip_offset, mip_size, mip_width, mip_height); get_mipmap_offset_size_and_dimensions(mip, mip_offset, mip_size, mip_width, mip_height);
@ -1151,7 +1144,7 @@ void Image::resize(int p_width, int p_height, Interpolation p_interpolation) {
if (i == 0) { if (i == 0) {
// Read from the first mipmap that will be interpolated // Read from the first mipmap that will be interpolated
// (if both levels are the same, we will not interpolate, but at least we'll sample from the right level) // (if both levels are the same, we will not interpolate, but at least we'll sample from the right level)
int offs; int64_t offs;
_get_mipmap_offset_and_size(mip1, offs, src_width, src_height); _get_mipmap_offset_and_size(mip1, offs, src_width, src_height);
src_ptr = r_ptr + offs; src_ptr = r_ptr + offs;
} else if (!interpolate_mipmaps) { } else if (!interpolate_mipmaps) {
@ -1159,7 +1152,7 @@ void Image::resize(int p_width, int p_height, Interpolation p_interpolation) {
break; break;
} else { } else {
// Switch to read from the second mipmap that will be interpolated // Switch to read from the second mipmap that will be interpolated
int offs; int64_t offs;
_get_mipmap_offset_and_size(mip2, offs, src_width, src_height); _get_mipmap_offset_and_size(mip2, offs, src_width, src_height);
src_ptr = r_ptr + offs; src_ptr = r_ptr + offs;
// Switch to write to the second destination image // Switch to write to the second destination image
@ -1599,9 +1592,9 @@ void Image::flip_x() {
} }
/// Get mipmap size and offset. /// Get mipmap size and offset.
int Image::_get_dst_image_size(int p_width, int p_height, Format p_format, int &r_mipmaps, int p_mipmaps, int *r_mm_width, int *r_mm_height) { int64_t Image::_get_dst_image_size(int p_width, int p_height, Format p_format, int &r_mipmaps, int p_mipmaps, int *r_mm_width, int *r_mm_height) {
// Data offset in mipmaps (including the original texture). // Data offset in mipmaps (including the original texture).
int size = 0; int64_t size = 0;
int w = p_width; int w = p_width;
int h = p_height; int h = p_height;
@ -1623,7 +1616,7 @@ int Image::_get_dst_image_size(int p_width, int p_height, Format p_format, int &
int bw = w % block != 0 ? w + (block - w % block) : w; int bw = w % block != 0 ? w + (block - w % block) : w;
int bh = h % block != 0 ? h + (block - h % block) : h; int bh = h % block != 0 ? h + (block - h % block) : h;
int s = bw * bh; int64_t s = bw * bh;
s *= pixsize; s *= pixsize;
s >>= pixshift; s >>= pixshift;
@ -1837,7 +1830,8 @@ Error Image::generate_mipmaps(bool p_renormalize) {
int prev_w = width; int prev_w = width;
for (int i = 1; i <= mmcount; i++) { for (int i = 1; i <= mmcount; i++) {
int ofs, w, h; int64_t ofs;
int w, h;
_get_mipmap_offset_and_size(i, ofs, w, h); _get_mipmap_offset_and_size(i, ofs, w, h);
switch (format) { switch (format) {
@ -1993,7 +1987,8 @@ Error Image::generate_mipmap_roughness(RoughnessChannel p_roughness_channel, con
uint8_t *base_ptr = data.ptrw(); uint8_t *base_ptr = data.ptrw();
for (int i = 1; i <= mmcount; i++) { for (int i = 1; i <= mmcount; i++) {
int ofs, w, h; int64_t ofs;
int w, h;
_get_mipmap_offset_and_size(i, ofs, w, h); _get_mipmap_offset_and_size(i, ofs, w, h);
uint8_t *ptr = &base_ptr[ofs]; uint8_t *ptr = &base_ptr[ofs];
@ -2102,21 +2097,6 @@ Error Image::generate_mipmap_roughness(RoughnessChannel p_roughness_channel, con
_set_color_at_ofs(ptr, pixel_ofs, c); _set_color_at_ofs(ptr, pixel_ofs, c);
} }
} }
#if 0
{
int size = get_mipmap_byte_size(i);
print_line("size for mimpap " + itos(i) + ": " + itos(size));
Vector<uint8_t> imgdata;
imgdata.resize(size);
uint8_t* wr = imgdata.ptrw();
memcpy(wr.ptr(), ptr, size);
wr = uint8_t*();
Ref<Image> im = Image::create_from_data(w, h, false, format, imgdata);
im->save_png("res://mipmap_" + itos(i) + ".png");
}
#endif
} }
return OK; return OK;
@ -2131,7 +2111,8 @@ void Image::clear_mipmaps() {
return; return;
} }
int ofs, w, h; int64_t ofs;
int w, h;
_get_mipmap_offset_and_size(1, ofs, w, h); _get_mipmap_offset_and_size(1, ofs, w, h);
data.resize(ofs); data.resize(ofs);
@ -2176,7 +2157,7 @@ void Image::initialize_data(int p_width, int p_height, bool p_use_mipmaps, Forma
ERR_FAIL_INDEX_MSG(p_format, FORMAT_MAX, "The Image format specified (" + itos(p_format) + ") is out of range. See Image's Format enum."); ERR_FAIL_INDEX_MSG(p_format, FORMAT_MAX, "The Image format specified (" + itos(p_format) + ") is out of range. See Image's Format enum.");
int mm = 0; int mm = 0;
int size = _get_dst_image_size(p_width, p_height, p_format, mm, p_use_mipmaps ? -1 : 0); int64_t size = _get_dst_image_size(p_width, p_height, p_format, mm, p_use_mipmaps ? -1 : 0);
data.resize(size); data.resize(size);
{ {
@ -2202,7 +2183,7 @@ void Image::initialize_data(int p_width, int p_height, bool p_use_mipmaps, Forma
ERR_FAIL_INDEX_MSG(p_format, FORMAT_MAX, "The Image format specified (" + itos(p_format) + ") is out of range. See Image's Format enum."); ERR_FAIL_INDEX_MSG(p_format, FORMAT_MAX, "The Image format specified (" + itos(p_format) + ") is out of range. See Image's Format enum.");
int mm; int mm;
int size = _get_dst_image_size(p_width, p_height, p_format, mm, p_use_mipmaps ? -1 : 0); int64_t size = _get_dst_image_size(p_width, p_height, p_format, mm, p_use_mipmaps ? -1 : 0);
if (unlikely(p_data.size() != size)) { if (unlikely(p_data.size() != size)) {
String description_mipmaps = get_format_name(p_format) + " "; String description_mipmaps = get_format_name(p_format) + " ";
@ -2405,7 +2386,7 @@ bool Image::is_invisible() const {
return false; return false;
} }
int len = data.size(); int64_t len = data.size();
if (len == 0) { if (len == 0) {
return true; return true;
@ -2445,7 +2426,7 @@ bool Image::is_invisible() const {
} }
Image::AlphaMode Image::detect_alpha() const { Image::AlphaMode Image::detect_alpha() const {
int len = data.size(); int64_t len = data.size();
if (len == 0) { if (len == 0) {
return ALPHA_NONE; return ALPHA_NONE;
@ -2597,7 +2578,7 @@ Size2i Image::get_image_mipmap_size(int p_width, int p_height, Format p_format,
return ret; return ret;
} }
int Image::get_image_mipmap_offset(int p_width, int p_height, Format p_format, int p_mipmap) { int64_t Image::get_image_mipmap_offset(int p_width, int p_height, Format p_format, int p_mipmap) {
if (p_mipmap <= 0) { if (p_mipmap <= 0) {
return 0; return 0;
} }
@ -2605,7 +2586,7 @@ int Image::get_image_mipmap_offset(int p_width, int p_height, Format p_format, i
return _get_dst_image_size(p_width, p_height, p_format, mm, p_mipmap - 1); return _get_dst_image_size(p_width, p_height, p_format, mm, p_mipmap - 1);
} }
int Image::get_image_mipmap_offset_and_dimensions(int p_width, int p_height, Format p_format, int p_mipmap, int &r_w, int &r_h) { int64_t Image::get_image_mipmap_offset_and_dimensions(int p_width, int p_height, Format p_format, int p_mipmap, int &r_w, int &r_h) {
if (p_mipmap <= 0) { if (p_mipmap <= 0) {
r_w = p_width; r_w = p_width;
r_h = p_height; r_h = p_height;
@ -3642,9 +3623,10 @@ Ref<Image> Image::rgbe_to_srgb() {
return new_image; return new_image;
} }
Ref<Image> Image::get_image_from_mipmap(int p_mipamp) const { Ref<Image> Image::get_image_from_mipmap(int p_mipmap) const {
int ofs, size, w, h; int64_t ofs, size;
get_mipmap_offset_size_and_dimensions(p_mipamp, ofs, size, w, h); int w, h;
get_mipmap_offset_size_and_dimensions(p_mipmap, ofs, size, w, h);
Vector<uint8_t> new_data; Vector<uint8_t> new_data;
new_data.resize(size); new_data.resize(size);

View File

@ -195,9 +195,9 @@ private:
data = p_image.data; data = p_image.data;
} }
_FORCE_INLINE_ void _get_mipmap_offset_and_size(int p_mipmap, int &r_offset, int &r_width, int &r_height) const; //get where the mipmap begins in data _FORCE_INLINE_ void _get_mipmap_offset_and_size(int p_mipmap, int64_t &r_offset, int &r_width, int &r_height) const; //get where the mipmap begins in data
static int _get_dst_image_size(int p_width, int p_height, Format p_format, int &r_mipmaps, int p_mipmaps = -1, int *r_mm_width = nullptr, int *r_mm_height = nullptr); static int64_t _get_dst_image_size(int p_width, int p_height, Format p_format, int &r_mipmaps, int p_mipmaps = -1, int *r_mm_width = nullptr, int *r_mm_height = nullptr);
bool _can_modify(Format p_format) const; bool _can_modify(Format p_format) const;
_FORCE_INLINE_ void _get_clipped_src_and_dest_rects(const Ref<Image> &p_src, const Rect2i &p_src_rect, const Point2i &p_dest, Rect2i &r_clipped_src_rect, Rect2i &r_clipped_dest_rect) const; _FORCE_INLINE_ void _get_clipped_src_and_dest_rects(const Ref<Image> &p_src, const Rect2i &p_src_rect, const Point2i &p_dest, Rect2i &r_clipped_src_rect, Rect2i &r_clipped_dest_rect) const;
@ -238,10 +238,12 @@ public:
*/ */
Format get_format() const; Format get_format() const;
int get_mipmap_byte_size(int p_mipmap) const; //get where the mipmap begins in data /**
int get_mipmap_offset(int p_mipmap) const; //get where the mipmap begins in data * Get where the mipmap begins in data.
void get_mipmap_offset_and_size(int p_mipmap, int &r_ofs, int &r_size) const; //get where the mipmap begins in data */
void get_mipmap_offset_size_and_dimensions(int p_mipmap, int &r_ofs, int &r_size, int &w, int &h) const; //get where the mipmap begins in data int64_t get_mipmap_offset(int p_mipmap) const;
void get_mipmap_offset_and_size(int p_mipmap, int64_t &r_ofs, int64_t &r_size) const;
void get_mipmap_offset_size_and_dimensions(int p_mipmap, int64_t &r_ofs, int64_t &r_size, int &w, int &h) const;
enum Image3DValidateError { enum Image3DValidateError {
VALIDATE_3D_OK, VALIDATE_3D_OK,
@ -354,8 +356,8 @@ public:
static int get_image_data_size(int p_width, int p_height, Format p_format, bool p_mipmaps = false); 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); static int get_image_required_mipmaps(int p_width, int p_height, Format p_format);
static Size2i get_image_mipmap_size(int p_width, int p_height, Format p_format, int p_mipmap); static Size2i get_image_mipmap_size(int p_width, int p_height, Format p_format, int p_mipmap);
static int get_image_mipmap_offset(int p_width, int p_height, Format p_format, int p_mipmap); static int64_t get_image_mipmap_offset(int p_width, int p_height, Format p_format, int p_mipmap);
static int get_image_mipmap_offset_and_dimensions(int p_width, int p_height, Format p_format, int p_mipmap, int &r_w, int &r_h); static int64_t get_image_mipmap_offset_and_dimensions(int p_width, int p_height, Format p_format, int p_mipmap, int &r_w, int &r_h);
enum CompressMode { enum CompressMode {
COMPRESS_S3TC, COMPRESS_S3TC,
@ -383,7 +385,7 @@ public:
void srgb_to_linear(); void srgb_to_linear();
void normal_map_to_xy(); void normal_map_to_xy();
Ref<Image> rgbe_to_srgb(); Ref<Image> rgbe_to_srgb();
Ref<Image> get_image_from_mipmap(int p_mipamp) const; Ref<Image> get_image_from_mipmap(int p_mipmap) const;
void bump_map_to_normal_map(float bump_scale = 1.0); void bump_map_to_normal_map(float bump_scale = 1.0);
void blit_rect(const Ref<Image> &p_src, const Rect2i &p_src_rect, const Point2i &p_dest); void blit_rect(const Ref<Image> &p_src, const Rect2i &p_src_rect, const Point2i &p_dest);

View File

@ -1491,7 +1491,7 @@ void TextureStorage::_texture_set_data(RID p_texture, const Ref<Image> &p_image,
int tsize = 0; int tsize = 0;
for (int i = 0; i < mipmaps; i++) { for (int i = 0; i < mipmaps; i++) {
int size, ofs; int64_t size, ofs;
img->get_mipmap_offset_and_size(i, ofs, size); img->get_mipmap_offset_and_size(i, ofs, size);
if (compressed) { if (compressed) {
glPixelStorei(GL_UNPACK_ALIGNMENT, 4); glPixelStorei(GL_UNPACK_ALIGNMENT, 4);

View File

@ -372,3 +372,11 @@ GH-93982
Validate extension JSON: Error: Field 'classes/Sprite3D/properties/frame_coords': type changed value in new API, from "Vector2" to "Vector2i". Validate extension JSON: Error: Field 'classes/Sprite3D/properties/frame_coords': type changed value in new API, from "Vector2" to "Vector2i".
The type was wrong to begin with and has been corrected. Vector2 and Vector2i are convertible, so it should be compatible. The type was wrong to begin with and has been corrected. Vector2 and Vector2i are convertible, so it should be compatible.
GH-94243
--------
Validate extension JSON: Error: Field 'classes/Image/methods/get_mipmap_offset/return_value': meta changed value in new API, from "int32" to "int64".
Type changed to int64_t to support baking large lightmaps.
No compatibility method needed, both GDExtension and C# generate it as int64_t anyway.

View File

@ -120,7 +120,8 @@ Vector<uint8_t> basis_universal_packer(const Ref<Image> &p_image, Image::UsedCha
Vector<uint32_t> mip_data_padded; Vector<uint32_t> mip_data_padded;
for (int32_t i = 0; i <= image->get_mipmap_count(); i++) { for (int32_t i = 0; i <= image->get_mipmap_count(); i++) {
int ofs, size, width, height; int64_t ofs, size;
int width, height;
image->get_mipmap_offset_size_and_dimensions(i, ofs, size, width, height); image->get_mipmap_offset_size_and_dimensions(i, ofs, size, width, height);
const uint8_t *image_mip_data = image_data.ptr() + ofs; const uint8_t *image_mip_data = image_data.ptr() + ofs;

View File

@ -77,10 +77,11 @@ void image_decompress_squish(Image *p_image) {
} }
for (int i = 0; i <= mm_count; i++) { for (int i = 0; i <= mm_count; i++) {
int src_ofs = 0, mipmap_size = 0, mipmap_w = 0, mipmap_h = 0; int64_t src_ofs = 0, mipmap_size = 0;
int mipmap_w = 0, mipmap_h = 0;
p_image->get_mipmap_offset_size_and_dimensions(i, src_ofs, mipmap_size, mipmap_w, mipmap_h); p_image->get_mipmap_offset_size_and_dimensions(i, src_ofs, mipmap_size, mipmap_w, mipmap_h);
int dst_ofs = Image::get_image_mipmap_offset(p_image->get_width(), p_image->get_height(), target_format, i); int64_t dst_ofs = Image::get_image_mipmap_offset(p_image->get_width(), p_image->get_height(), target_format, i);
squish::DecompressImage(&wb[dst_ofs], w, h, &rb[src_ofs], squish_flags); squish::DecompressImage(&wb[dst_ofs], w, h, &rb[src_ofs], squish_flags);
w >>= 1; w >>= 1;

View File

@ -355,8 +355,8 @@ TEST_CASE("[Image] Custom mipmaps") {
uint8_t *data_ptr = data.ptrw(); uint8_t *data_ptr = data.ptrw();
for (int mip = 0; mip < mipmaps; mip++) { for (int mip = 0; mip < mipmaps; mip++) {
int mip_offset = 0; int64_t mip_offset = 0;
int mip_size = 0; int64_t mip_size = 0;
image->get_mipmap_offset_and_size(mip, mip_offset, mip_size); image->get_mipmap_offset_and_size(mip, mip_offset, mip_size);
for (int i = 0; i < mip_size; i++) { for (int i = 0; i < mip_size; i++) {
@ -378,8 +378,8 @@ TEST_CASE("[Image] Custom mipmaps") {
const uint8_t *data_ptr = data.ptr(); const uint8_t *data_ptr = data.ptr();
for (int mip = 0; mip < mipmaps; mip++) { for (int mip = 0; mip < mipmaps; mip++) {
int mip_offset = 0; int64_t mip_offset = 0;
int mip_size = 0; int64_t mip_size = 0;
image_bytes->get_mipmap_offset_and_size(mip, mip_offset, mip_size); image_bytes->get_mipmap_offset_and_size(mip, mip_offset, mip_size);
for (int i = 0; i < mip_size; i++) { for (int i = 0; i < mip_size; i++) {
@ -402,8 +402,8 @@ TEST_CASE("[Image] Custom mipmaps") {
const uint8_t *data_ptr = data.ptr(); const uint8_t *data_ptr = data.ptr();
for (int mip = 0; mip < mipmaps; mip++) { for (int mip = 0; mip < mipmaps; mip++) {
int mip_offset = 0; int64_t mip_offset = 0;
int mip_size = 0; int64_t mip_size = 0;
image_rgbaf->get_mipmap_offset_and_size(mip, mip_offset, mip_size); image_rgbaf->get_mipmap_offset_and_size(mip, mip_offset, mip_size);
for (int i = 0; i < mip_size; i += 4) { for (int i = 0; i < mip_size; i += 4) {