Bladeren bron

Merge pull request #46054 from JFonS/cpu_lightmapper_disk

[3.2] Reduce lightmaps file size.
Rémi Verschelde 4 jaren geleden
bovenliggende
commit
77438f7a45

+ 6 - 0
doc/classes/BakedLightmap.xml

@@ -78,9 +78,15 @@
 		<member name="quality" type="int" setter="set_bake_quality" getter="get_bake_quality" enum="BakedLightmap.BakeQuality" default="1">
 		<member name="quality" type="int" setter="set_bake_quality" getter="get_bake_quality" enum="BakedLightmap.BakeQuality" default="1">
 			Determines the amount of samples per texel used in indrect light baking. The amount of samples for each quality level can be configured in the project settings.
 			Determines the amount of samples per texel used in indrect light baking. The amount of samples for each quality level can be configured in the project settings.
 		</member>
 		</member>
+		<member name="use_color" type="bool" setter="set_use_color" getter="is_using_color" default="true">
+			Store full color values in the lightmap textures. When disabled, lightmap textures will store a single brightness channel. Can be disabled to reduce disk usage if the scene contains only white lights or you don't mind losing color information in indirect lighting.
+		</member>
 		<member name="use_denoiser" type="bool" setter="set_use_denoiser" getter="is_using_denoiser" default="true">
 		<member name="use_denoiser" type="bool" setter="set_use_denoiser" getter="is_using_denoiser" default="true">
 			When enabled, a lightmap denoiser will be used to reduce the noise inherent to Monte Carlo based global illumination.
 			When enabled, a lightmap denoiser will be used to reduce the noise inherent to Monte Carlo based global illumination.
 		</member>
 		</member>
+		<member name="use_hdr" type="bool" setter="set_use_hdr" getter="is_using_hdr" default="true">
+			Store the lightmap textures in a Hight Dynamic Range format (EXR). Can be disabled to reduce disk usage, but light values over 1.0 will be clamped and you may see banding caused by the reduced precision.
+		</member>
 	</members>
 	</members>
 	<constants>
 	<constants>
 		<constant name="BAKE_QUALITY_LOW" value="0" enum="BakeQuality">
 		<constant name="BAKE_QUALITY_LOW" value="0" enum="BakeQuality">

+ 6 - 0
doc/classes/BakedLightmapData.xml

@@ -23,6 +23,12 @@
 			<description>
 			<description>
 			</description>
 			</description>
 		</method>
 		</method>
+		<method name="clear_data">
+			<return type="void">
+			</return>
+			<description>
+			</description>
+		</method>
 		<method name="clear_users">
 		<method name="clear_users">
 			<return type="void">
 			<return type="void">
 			</return>
 			</return>

+ 3 - 0
modules/denoise/SCsub

@@ -108,6 +108,9 @@ env_thirdparty = env_oidn.Clone()
 env_thirdparty.disable_warnings()
 env_thirdparty.disable_warnings()
 env_thirdparty.add_source_files(env.modules_sources, thirdparty_sources)
 env_thirdparty.add_source_files(env.modules_sources, thirdparty_sources)
 
 
+if env["platform"] == "windows" and not env.msvc:
+    env_thirdparty.Append(CPPFLAGS=["-mstackrealign"])
+
 weights_in_path = thirdparty_dir + "weights/rtlightmap_hdr.tza"
 weights_in_path = thirdparty_dir + "weights/rtlightmap_hdr.tza"
 weights_out_path = thirdparty_dir + "weights/rtlightmap_hdr.gen.cpp"
 weights_out_path = thirdparty_dir + "weights/rtlightmap_hdr.gen.cpp"
 
 

+ 1 - 3
modules/denoise/denoise_wrapper.cpp

@@ -43,9 +43,7 @@ void *oidn_denoiser_init() {
 bool oidn_denoise(void *deviceptr, float *p_floats, int p_width, int p_height) {
 bool oidn_denoise(void *deviceptr, float *p_floats, int p_width, int p_height) {
 	OIDNDeviceImpl *device = (OIDNDeviceImpl *)deviceptr;
 	OIDNDeviceImpl *device = (OIDNDeviceImpl *)deviceptr;
 	OIDNFilter filter = oidnNewFilter(device, "RTLightmap");
 	OIDNFilter filter = oidnNewFilter(device, "RTLightmap");
-	void *input_buffer = memalloc(p_width * p_height * 3 * sizeof(float));
-	copymem(input_buffer, p_floats, p_width * p_height * 3 * sizeof(float));
-	oidnSetSharedFilterImage(filter, "color", input_buffer, OIDN_FORMAT_FLOAT3, p_width, p_height, 0, 0, 0);
+	oidnSetSharedFilterImage(filter, "color", (void *)p_floats, OIDN_FORMAT_FLOAT3, p_width, p_height, 0, 0, 0);
 	oidnSetSharedFilterImage(filter, "output", (void *)p_floats, OIDN_FORMAT_FLOAT3, p_width, p_height, 0, 0, 0);
 	oidnSetSharedFilterImage(filter, "output", (void *)p_floats, OIDN_FORMAT_FLOAT3, p_width, p_height, 0, 0, 0);
 	oidnSetFilter1b(filter, "hdr", true);
 	oidnSetFilter1b(filter, "hdr", true);
 	oidnCommitFilter(filter);
 	oidnCommitFilter(filter);

+ 2 - 0
modules/lightmapper_cpu/lightmapper_cpu.cpp

@@ -1369,6 +1369,7 @@ LightmapperCPU::BakeError LightmapperCPU::bake(BakeQuality p_quality, bool p_use
 	if (parameters.environment_panorama.is_valid()) {
 	if (parameters.environment_panorama.is_valid()) {
 		parameters.environment_panorama->lock();
 		parameters.environment_panorama->lock();
 	}
 	}
+
 	for (unsigned int i = 0; i < mesh_instances.size(); i++) {
 	for (unsigned int i = 0; i < mesh_instances.size(); i++) {
 
 
 		if (!mesh_instances[i].generate_lightmap) {
 		if (!mesh_instances[i].generate_lightmap) {
@@ -1389,6 +1390,7 @@ LightmapperCPU::BakeError LightmapperCPU::bake(BakeQuality p_quality, bool p_use
 			}
 			}
 		}
 		}
 	}
 	}
+
 	if (parameters.environment_panorama.is_valid()) {
 	if (parameters.environment_panorama.is_valid()) {
 		parameters.environment_panorama->unlock();
 		parameters.environment_panorama->unlock();
 	}
 	}

+ 14 - 3
modules/tinyexr/SCsub

@@ -6,6 +6,9 @@ Import("env_modules")
 env_tinyexr = env_modules.Clone()
 env_tinyexr = env_modules.Clone()
 
 
 # Thirdparty source files
 # Thirdparty source files
+
+thirdparty_obj = []
+
 # Not unbundled for now as they are not commonly available as shared library
 # Not unbundled for now as they are not commonly available as shared library
 thirdparty_dir = "#thirdparty/tinyexr/"
 thirdparty_dir = "#thirdparty/tinyexr/"
 thirdparty_sources = [
 thirdparty_sources = [
@@ -20,7 +23,15 @@ env_tinyexr.Append(CPPDEFINES=["TINYEXR_USE_THREAD"])
 
 
 env_thirdparty = env_tinyexr.Clone()
 env_thirdparty = env_tinyexr.Clone()
 env_thirdparty.disable_warnings()
 env_thirdparty.disable_warnings()
-env_thirdparty.add_source_files(env.modules_sources, thirdparty_sources)
+env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources)
+env.modules_sources += thirdparty_obj
+
+# Godot source files
+
+module_obj = []
+
+env_tinyexr.add_source_files(module_obj, "*.cpp")
+env.modules_sources += module_obj
 
 
-# Godot's own source files
-env_tinyexr.add_source_files(env.modules_sources, "*.cpp")
+# Needed to force rebuilding the module files when the thirdparty library is updated.
+env.Depends(module_obj, thirdparty_obj)

+ 120 - 70
modules/tinyexr/image_loader_tinyexr.cpp

@@ -42,7 +42,8 @@ Error ImageLoaderTinyEXR::load_image(Ref<Image> p_image, FileAccess *f, bool p_f
 	ERR_FAIL_COND_V(src_image_len == 0, ERR_FILE_CORRUPT);
 	ERR_FAIL_COND_V(src_image_len == 0, ERR_FILE_CORRUPT);
 	src_image.resize(src_image_len);
 	src_image.resize(src_image_len);
 
 
-	PoolVector<uint8_t>::Write w = src_image.write();
+	PoolVector<uint8_t>::Write img_write = src_image.write();
+	uint8_t *w = img_write.ptr();
 
 
 	f->get_buffer(&w[0], src_image_len);
 	f->get_buffer(&w[0], src_image_len);
 
 
@@ -56,36 +57,37 @@ Error ImageLoaderTinyEXR::load_image(Ref<Image> p_image, FileAccess *f, bool p_f
 	EXRVersion exr_version;
 	EXRVersion exr_version;
 	EXRImage exr_image;
 	EXRImage exr_image;
 	EXRHeader exr_header;
 	EXRHeader exr_header;
-	const char *err = NULL;
+	const char *err = nullptr;
 
 
 	InitEXRHeader(&exr_header);
 	InitEXRHeader(&exr_header);
 
 
-	int ret = ParseEXRVersionFromMemory(&exr_version, w.ptr(), src_image_len);
+	int ret = ParseEXRVersionFromMemory(&exr_version, w, src_image_len);
 	if (ret != TINYEXR_SUCCESS) {
 	if (ret != TINYEXR_SUCCESS) {
-
 		return ERR_FILE_CORRUPT;
 		return ERR_FILE_CORRUPT;
 	}
 	}
 
 
-	ret = ParseEXRHeaderFromMemory(&exr_header, &exr_version, w.ptr(), src_image_len, &err);
+	ret = ParseEXRHeaderFromMemory(&exr_header, &exr_version, w, src_image_len, &err);
 	if (ret != TINYEXR_SUCCESS) {
 	if (ret != TINYEXR_SUCCESS) {
 		if (err) {
 		if (err) {
-			ERR_PRINTS(String(err));
+			ERR_PRINT(String(err));
 		}
 		}
 		return ERR_FILE_CORRUPT;
 		return ERR_FILE_CORRUPT;
 	}
 	}
 
 
 	// Read HALF channel as FLOAT. (GH-13490)
 	// Read HALF channel as FLOAT. (GH-13490)
+	bool use_float16 = false;
 	for (int i = 0; i < exr_header.num_channels; i++) {
 	for (int i = 0; i < exr_header.num_channels; i++) {
 		if (exr_header.pixel_types[i] == TINYEXR_PIXELTYPE_HALF) {
 		if (exr_header.pixel_types[i] == TINYEXR_PIXELTYPE_HALF) {
+			use_float16 = true;
 			exr_header.requested_pixel_types[i] = TINYEXR_PIXELTYPE_FLOAT;
 			exr_header.requested_pixel_types[i] = TINYEXR_PIXELTYPE_FLOAT;
 		}
 		}
 	}
 	}
 
 
 	InitEXRImage(&exr_image);
 	InitEXRImage(&exr_image);
-	ret = LoadEXRImageFromMemory(&exr_image, &exr_header, w.ptr(), src_image_len, &err);
+	ret = LoadEXRImageFromMemory(&exr_image, &exr_header, w, src_image_len, &err);
 	if (ret != TINYEXR_SUCCESS) {
 	if (ret != TINYEXR_SUCCESS) {
 		if (err) {
 		if (err) {
-			ERR_PRINTS(String(err));
+			ERR_PRINT(String(err));
 		}
 		}
 		return ERR_FILE_CORRUPT;
 		return ERR_FILE_CORRUPT;
 	}
 	}
@@ -104,33 +106,10 @@ Error ImageLoaderTinyEXR::load_image(Ref<Image> p_image, FileAccess *f, bool p_f
 			idxB = c;
 			idxB = c;
 		} else if (strcmp(exr_header.channels[c].name, "A") == 0) {
 		} else if (strcmp(exr_header.channels[c].name, "A") == 0) {
 			idxA = c;
 			idxA = c;
-		}
-	}
-
-	if (exr_header.num_channels == 1) {
-		// Grayscale channel only.
-		idxR = 0;
-		idxG = 0;
-		idxB = 0;
-		idxA = 0;
-	} else {
-		// Assume RGB(A)
-		if (idxR == -1) {
-			ERR_PRINT("TinyEXR: R channel not found.");
-			// @todo { free exr_image }
-			return ERR_FILE_CORRUPT;
-		}
-
-		if (idxG == -1) {
-			ERR_PRINT("TinyEXR: G channel not found.");
-			// @todo { free exr_image }
-			return ERR_FILE_CORRUPT;
-		}
-
-		if (idxB == -1) {
-			ERR_PRINT("TinyEXR: B channel not found.");
-			// @todo { free exr_image }
-			return ERR_FILE_CORRUPT;
+		} else if (strcmp(exr_header.channels[c].name, "Y") == 0) {
+			idxR = c;
+			idxG = c;
+			idxB = c;
 		}
 		}
 	}
 	}
 
 
@@ -140,16 +119,27 @@ Error ImageLoaderTinyEXR::load_image(Ref<Image> p_image, FileAccess *f, bool p_f
 	Image::Format format;
 	Image::Format format;
 	int output_channels = 0;
 	int output_channels = 0;
 
 
+	int channel_size = use_float16 ? 2 : 4;
 	if (idxA != -1) {
 	if (idxA != -1) {
-
-		imgdata.resize(exr_image.width * exr_image.height * 8); //RGBA16
-		format = Image::FORMAT_RGBAH;
+		imgdata.resize(exr_image.width * exr_image.height * 4 * channel_size); //RGBA
+		format = use_float16 ? Image::FORMAT_RGBAH : Image::FORMAT_RGBAF;
 		output_channels = 4;
 		output_channels = 4;
-	} else {
-
-		imgdata.resize(exr_image.width * exr_image.height * 6); //RGB16
-		format = Image::FORMAT_RGBH;
+	} else if (idxB != -1) {
+		ERR_FAIL_COND_V(idxG == -1, ERR_FILE_CORRUPT);
+		ERR_FAIL_COND_V(idxR == -1, ERR_FILE_CORRUPT);
+		imgdata.resize(exr_image.width * exr_image.height * 3 * channel_size); //RGB
+		format = use_float16 ? Image::FORMAT_RGBH : Image::FORMAT_RGBF;
 		output_channels = 3;
 		output_channels = 3;
+	} else if (idxG != -1) {
+		ERR_FAIL_COND_V(idxR == -1, ERR_FILE_CORRUPT);
+		imgdata.resize(exr_image.width * exr_image.height * 2 * channel_size); //RG
+		format = use_float16 ? Image::FORMAT_RGH : Image::FORMAT_RGF;
+		output_channels = 2;
+	} else {
+		ERR_FAIL_COND_V(idxR == -1, ERR_FILE_CORRUPT);
+		imgdata.resize(exr_image.width * exr_image.height * 1 * channel_size); //R
+		format = use_float16 ? Image::FORMAT_RH : Image::FORMAT_RF;
+		output_channels = 1;
 	}
 	}
 
 
 	EXRTile single_image_tile;
 	EXRTile single_image_tile;
@@ -179,54 +169,115 @@ Error ImageLoaderTinyEXR::load_image(Ref<Image> p_image, FileAccess *f, bool p_f
 		exr_tiles = exr_image.tiles;
 		exr_tiles = exr_image.tiles;
 	}
 	}
 
 
+	//print_line("reading format: " + Image::get_format_name(format));
 	{
 	{
-		PoolVector<uint8_t>::Write wd = imgdata.write();
-		uint16_t *iw = (uint16_t *)wd.ptr();
+
+		PoolVector<uint8_t>::Write imgdata_write = imgdata.write();
+		uint8_t *wd = imgdata_write.ptr();
+		uint16_t *iw16 = (uint16_t *)wd;
+		float *iw32 = (float *)wd;
 
 
 		// Assume `out_rgba` have enough memory allocated.
 		// Assume `out_rgba` have enough memory allocated.
 		for (int tile_index = 0; tile_index < num_tiles; tile_index++) {
 		for (int tile_index = 0; tile_index < num_tiles; tile_index++) {
-
 			const EXRTile &tile = exr_tiles[tile_index];
 			const EXRTile &tile = exr_tiles[tile_index];
 
 
 			int tw = tile.width;
 			int tw = tile.width;
 			int th = tile.height;
 			int th = tile.height;
 
 
 			const float *r_channel_start = reinterpret_cast<const float *>(tile.images[idxR]);
 			const float *r_channel_start = reinterpret_cast<const float *>(tile.images[idxR]);
-			const float *g_channel_start = reinterpret_cast<const float *>(tile.images[idxG]);
-			const float *b_channel_start = reinterpret_cast<const float *>(tile.images[idxB]);
-			const float *a_channel_start = NULL;
+			const float *g_channel_start = nullptr;
+			const float *b_channel_start = nullptr;
+			const float *a_channel_start = nullptr;
 
 
+			if (idxG != -1) {
+				g_channel_start = reinterpret_cast<const float *>(tile.images[idxG]);
+			}
+			if (idxB != -1) {
+				b_channel_start = reinterpret_cast<const float *>(tile.images[idxB]);
+			}
 			if (idxA != -1) {
 			if (idxA != -1) {
 				a_channel_start = reinterpret_cast<const float *>(tile.images[idxA]);
 				a_channel_start = reinterpret_cast<const float *>(tile.images[idxA]);
 			}
 			}
 
 
-			uint16_t *first_row_w = iw + (tile.offset_y * tile_height * exr_image.width + tile.offset_x * tile_width) * output_channels;
+			uint16_t *first_row_w16 = iw16 + (tile.offset_y * tile_height * exr_image.width + tile.offset_x * tile_width) * output_channels;
+			float *first_row_w32 = iw32 + (tile.offset_y * tile_height * exr_image.width + tile.offset_x * tile_width) * output_channels;
 
 
 			for (int y = 0; y < th; y++) {
 			for (int y = 0; y < th; y++) {
 				const float *r_channel = r_channel_start + y * tile_width;
 				const float *r_channel = r_channel_start + y * tile_width;
-				const float *g_channel = g_channel_start + y * tile_width;
-				const float *b_channel = b_channel_start + y * tile_width;
-				const float *a_channel = NULL;
-
+				const float *g_channel = nullptr;
+				const float *b_channel = nullptr;
+				const float *a_channel = nullptr;
+				if (g_channel_start) {
+					g_channel = g_channel_start + y * tile_width;
+				}
+				if (b_channel_start) {
+					b_channel = b_channel_start + y * tile_width;
+				}
 				if (a_channel_start) {
 				if (a_channel_start) {
 					a_channel = a_channel_start + y * tile_width;
 					a_channel = a_channel_start + y * tile_width;
 				}
 				}
 
 
-				uint16_t *row_w = first_row_w + (y * exr_image.width * output_channels);
-
-				for (int x = 0; x < tw; x++) {
-
-					Color color(*r_channel++, *g_channel++, *b_channel++);
-
-					if (p_force_linear)
-						color = color.to_linear();
-
-					*row_w++ = Math::make_half_float(color.r);
-					*row_w++ = Math::make_half_float(color.g);
-					*row_w++ = Math::make_half_float(color.b);
-
-					if (idxA != -1) {
-						*row_w++ = Math::make_half_float(*a_channel++);
+				if (use_float16) {
+					uint16_t *row_w = first_row_w16 + (y * exr_image.width * output_channels);
+
+					for (int x = 0; x < tw; x++) {
+						Color color;
+						color.r = *r_channel++;
+						if (g_channel) {
+							color.g = *g_channel++;
+						}
+						if (b_channel) {
+							color.b = *b_channel++;
+						}
+						if (a_channel) {
+							color.a = *a_channel++;
+						}
+
+						if (p_force_linear) {
+							color = color.to_linear();
+						}
+
+						*row_w++ = Math::make_half_float(color.r);
+						if (g_channel) {
+							*row_w++ = Math::make_half_float(color.g);
+						}
+						if (b_channel) {
+							*row_w++ = Math::make_half_float(color.b);
+						}
+						if (a_channel) {
+							*row_w++ = Math::make_half_float(color.a);
+						}
+					}
+				} else {
+					float *row_w = first_row_w32 + (y * exr_image.width * output_channels);
+
+					for (int x = 0; x < tw; x++) {
+						Color color;
+						color.r = *r_channel++;
+						if (g_channel) {
+							color.g = *g_channel++;
+						}
+						if (b_channel) {
+							color.b = *b_channel++;
+						}
+						if (a_channel) {
+							color.a = *a_channel++;
+						}
+
+						if (p_force_linear) {
+							color = color.to_linear();
+						}
+
+						*row_w++ = color.r;
+						if (g_channel) {
+							*row_w++ = color.g;
+						}
+						if (b_channel) {
+							*row_w++ = color.b;
+						}
+						if (a_channel) {
+							*row_w++ = color.a;
+						}
 					}
 					}
 				}
 				}
 			}
 			}
@@ -235,7 +286,7 @@ Error ImageLoaderTinyEXR::load_image(Ref<Image> p_image, FileAccess *f, bool p_f
 
 
 	p_image->create(exr_image.width, exr_image.height, false, format, imgdata);
 	p_image->create(exr_image.width, exr_image.height, false, format, imgdata);
 
 
-	w.release();
+	img_write.release();
 
 
 	FreeEXRHeader(&exr_header);
 	FreeEXRHeader(&exr_header);
 	FreeEXRImage(&exr_image);
 	FreeEXRImage(&exr_image);
@@ -244,7 +295,6 @@ Error ImageLoaderTinyEXR::load_image(Ref<Image> p_image, FileAccess *f, bool p_f
 }
 }
 
 
 void ImageLoaderTinyEXR::get_recognized_extensions(List<String> *p_extensions) const {
 void ImageLoaderTinyEXR::get_recognized_extensions(List<String> *p_extensions) const {
-
 	p_extensions->push_back("exr");
 	p_extensions->push_back("exr");
 }
 }
 
 

+ 0 - 1
modules/tinyexr/image_loader_tinyexr.h

@@ -34,7 +34,6 @@
 #include "core/io/image_loader.h"
 #include "core/io/image_loader.h"
 
 
 class ImageLoaderTinyEXR : public ImageFormatLoader {
 class ImageLoaderTinyEXR : public ImageFormatLoader {
-
 public:
 public:
 	virtual Error load_image(Ref<Image> p_image, FileAccess *f, bool p_force_linear, float p_scale);
 	virtual Error load_image(Ref<Image> p_image, FileAccess *f, bool p_force_linear, float p_scale);
 	virtual void get_recognized_extensions(List<String> *p_extensions) const;
 	virtual void get_recognized_extensions(List<String> *p_extensions) const;

+ 22 - 14
modules/tinyexr/image_saver_tinyexr.cpp

@@ -58,7 +58,8 @@ static bool is_supported_format(Image::Format p_format) {
 enum SrcPixelType {
 enum SrcPixelType {
 	SRC_FLOAT,
 	SRC_FLOAT,
 	SRC_HALF,
 	SRC_HALF,
-	SRC_BYTE
+	SRC_BYTE,
+	SRC_UNSUPPORTED
 };
 };
 
 
 static SrcPixelType get_source_pixel_type(Image::Format p_format) {
 static SrcPixelType get_source_pixel_type(Image::Format p_format) {
@@ -79,7 +80,7 @@ static SrcPixelType get_source_pixel_type(Image::Format p_format) {
 		case Image::FORMAT_RGBA8:
 		case Image::FORMAT_RGBA8:
 			return SRC_BYTE;
 			return SRC_BYTE;
 		default:
 		default:
-			CRASH_NOW();
+			return SRC_UNSUPPORTED;
 	}
 	}
 }
 }
 
 
@@ -101,7 +102,7 @@ static int get_target_pixel_type(Image::Format p_format) {
 		case Image::FORMAT_RGBA8:
 		case Image::FORMAT_RGBA8:
 			return TINYEXR_PIXELTYPE_HALF;
 			return TINYEXR_PIXELTYPE_HALF;
 		default:
 		default:
-			CRASH_NOW();
+			return -1;
 	}
 	}
 }
 }
 
 
@@ -112,7 +113,7 @@ static int get_pixel_type_size(int p_pixel_type) {
 		case TINYEXR_PIXELTYPE_FLOAT:
 		case TINYEXR_PIXELTYPE_FLOAT:
 			return 4;
 			return 4;
 	}
 	}
-	CRASH_NOW();
+	return -1;
 }
 }
 
 
 static int get_channel_count(Image::Format p_format) {
 static int get_channel_count(Image::Format p_format) {
@@ -134,12 +135,11 @@ static int get_channel_count(Image::Format p_format) {
 		case Image::FORMAT_RGBA8:
 		case Image::FORMAT_RGBA8:
 			return 4;
 			return 4;
 		default:
 		default:
-			CRASH_NOW();
+			return -1;
 	}
 	}
 }
 }
 
 
 Error save_exr(const String &p_path, const Ref<Image> &p_img, bool p_grayscale) {
 Error save_exr(const String &p_path, const Ref<Image> &p_img, bool p_grayscale) {
-
 	Image::Format format = p_img->get_format();
 	Image::Format format = p_img->get_format();
 
 
 	if (!is_supported_format(format)) {
 	if (!is_supported_format(format)) {
@@ -173,11 +173,15 @@ Error save_exr(const String &p_path, const Ref<Image> &p_img, bool p_grayscale)
 	};
 	};
 
 
 	int channel_count = get_channel_count(format);
 	int channel_count = get_channel_count(format);
+	ERR_FAIL_COND_V(channel_count < 0, ERR_UNAVAILABLE);
 	ERR_FAIL_COND_V(p_grayscale && channel_count != 1, ERR_INVALID_PARAMETER);
 	ERR_FAIL_COND_V(p_grayscale && channel_count != 1, ERR_INVALID_PARAMETER);
 
 
 	int target_pixel_type = get_target_pixel_type(format);
 	int target_pixel_type = get_target_pixel_type(format);
+	ERR_FAIL_COND_V(target_pixel_type < 0, ERR_UNAVAILABLE);
 	int target_pixel_type_size = get_pixel_type_size(target_pixel_type);
 	int target_pixel_type_size = get_pixel_type_size(target_pixel_type);
+	ERR_FAIL_COND_V(target_pixel_type_size < 0, ERR_UNAVAILABLE);
 	SrcPixelType src_pixel_type = get_source_pixel_type(format);
 	SrcPixelType src_pixel_type = get_source_pixel_type(format);
+	ERR_FAIL_COND_V(src_pixel_type == SRC_UNSUPPORTED, ERR_UNAVAILABLE);
 	const int pixel_count = p_img->get_width() * p_img->get_height();
 	const int pixel_count = p_img->get_width() * p_img->get_height();
 
 
 	const int *channel_mapping = channel_mappings[channel_count - 1];
 	const int *channel_mapping = channel_mappings[channel_count - 1];
@@ -187,7 +191,6 @@ Error save_exr(const String &p_path, const Ref<Image> &p_img, bool p_grayscale)
 		PoolByteArray::Read src_r = src_data.read();
 		PoolByteArray::Read src_r = src_data.read();
 
 
 		for (int channel_index = 0; channel_index < channel_count; ++channel_index) {
 		for (int channel_index = 0; channel_index < channel_count; ++channel_index) {
-
 			// De-interleave channels
 			// De-interleave channels
 
 
 			PoolByteArray &dst = channels[channel_index];
 			PoolByteArray &dst = channels[channel_index];
@@ -196,7 +199,6 @@ Error save_exr(const String &p_path, const Ref<Image> &p_img, bool p_grayscale)
 			PoolByteArray::Write dst_w = dst.write();
 			PoolByteArray::Write dst_w = dst.write();
 
 
 			if (src_pixel_type == SRC_FLOAT && target_pixel_type == TINYEXR_PIXELTYPE_FLOAT) {
 			if (src_pixel_type == SRC_FLOAT && target_pixel_type == TINYEXR_PIXELTYPE_FLOAT) {
-
 				// Note: we don't save mipmaps
 				// Note: we don't save mipmaps
 				CRASH_COND(src_data.size() < pixel_count * channel_count * target_pixel_type_size);
 				CRASH_COND(src_data.size() < pixel_count * channel_count * target_pixel_type_size);
 
 
@@ -208,7 +210,6 @@ Error save_exr(const String &p_path, const Ref<Image> &p_img, bool p_grayscale)
 				}
 				}
 
 
 			} else if (src_pixel_type == SRC_HALF && target_pixel_type == TINYEXR_PIXELTYPE_HALF) {
 			} else if (src_pixel_type == SRC_HALF && target_pixel_type == TINYEXR_PIXELTYPE_HALF) {
-
 				CRASH_COND(src_data.size() < pixel_count * channel_count * target_pixel_type_size);
 				CRASH_COND(src_data.size() < pixel_count * channel_count * target_pixel_type_size);
 
 
 				const uint16_t *src_rp = (uint16_t *)src_r.ptr();
 				const uint16_t *src_rp = (uint16_t *)src_r.ptr();
@@ -219,7 +220,6 @@ Error save_exr(const String &p_path, const Ref<Image> &p_img, bool p_grayscale)
 				}
 				}
 
 
 			} else if (src_pixel_type == SRC_BYTE && target_pixel_type == TINYEXR_PIXELTYPE_HALF) {
 			} else if (src_pixel_type == SRC_BYTE && target_pixel_type == TINYEXR_PIXELTYPE_HALF) {
-
 				CRASH_COND(src_data.size() < pixel_count * channel_count);
 				CRASH_COND(src_data.size() < pixel_count * channel_count);
 
 
 				const uint8_t *src_rp = (uint8_t *)src_r.ptr();
 				const uint8_t *src_rp = (uint8_t *)src_r.ptr();
@@ -262,13 +262,21 @@ Error save_exr(const String &p_path, const Ref<Image> &p_img, bool p_grayscale)
 	header.channels = channel_infos;
 	header.channels = channel_infos;
 	header.pixel_types = pixel_types;
 	header.pixel_types = pixel_types;
 	header.requested_pixel_types = requested_pixel_types;
 	header.requested_pixel_types = requested_pixel_types;
+	header.compression_type = TINYEXR_COMPRESSIONTYPE_PIZ;
+
+	unsigned char *mem = nullptr;
+	const char *err = nullptr;
+
+	size_t bytes = SaveEXRImageToMemory(&image, &header, &mem, &err);
 
 
-	CharString utf8_filename = p_path.utf8();
-	const char *err;
-	int ret = SaveEXRImageToFile(&image, &header, utf8_filename.ptr(), &err);
-	if (ret != TINYEXR_SUCCESS) {
+	if (bytes == 0) {
 		print_error(String("Saving EXR failed. Error: {0}").format(varray(err)));
 		print_error(String("Saving EXR failed. Error: {0}").format(varray(err)));
 		return ERR_FILE_CANT_WRITE;
 		return ERR_FILE_CANT_WRITE;
+	} else {
+		FileAccessRef ref = FileAccess::open(p_path, FileAccess::WRITE);
+		ERR_FAIL_COND_V(!ref, ERR_FILE_CANT_WRITE);
+		ref->store_buffer(mem, bytes);
+		free(mem);
 	}
 	}
 
 
 	return OK;
 	return OK;

+ 85 - 15
scene/3d/baked_lightmap.cpp

@@ -516,6 +516,55 @@ void BakedLightmap::_get_material_images(const MeshesFound &p_found_mesh, Lightm
 	}
 	}
 }
 }
 
 
+void BakedLightmap::_save_image(String &r_base_path, Ref<Image> r_img, bool p_use_srgb) {
+	if (use_hdr) {
+		r_base_path += ".exr";
+	} else {
+		r_base_path += ".png";
+	}
+
+	String relative_path = r_base_path;
+	if (relative_path.begins_with("res://")) {
+		relative_path = relative_path.substr(6, relative_path.length());
+	}
+
+	bool hdr_grayscale = use_hdr && !use_color;
+
+	if (p_use_srgb || hdr_grayscale) {
+		r_img->lock();
+		for (int i = 0; i < r_img->get_height(); i++) {
+			for (int j = 0; j < r_img->get_width(); j++) {
+				Color c = r_img->get_pixel(j, i);
+
+				if (hdr_grayscale) {
+					c = Color(c.get_v(), 0.0f, 0.0f);
+				}
+
+				if (p_use_srgb) {
+					c = c.to_srgb();
+				}
+
+				r_img->set_pixel(j, i, c);
+			}
+		}
+		r_img->unlock();
+	}
+
+	if (!use_color) {
+		if (use_hdr) {
+			r_img->convert(Image::FORMAT_RH);
+		} else {
+			r_img->convert(Image::FORMAT_L8);
+		}
+	}
+
+	if (use_hdr) {
+		r_img->save_exr(relative_path, !use_color);
+	} else {
+		r_img->save_png(relative_path);
+	}
+}
+
 bool BakedLightmap::_lightmap_bake_step_function(float p_completion, const String &p_text, void *ud, bool p_refresh) {
 bool BakedLightmap::_lightmap_bake_step_function(float p_completion, const String &p_text, void *ud, bool p_refresh) {
 	BakeStepUD *bsud = (BakeStepUD *)ud;
 	BakeStepUD *bsud = (BakeStepUD *)ud;
 	bool ret = false;
 	bool ret = false;
@@ -893,6 +942,8 @@ BakedLightmap::BakeError BakedLightmap::bake(Node *p_from_node, String p_data_sa
 		images.push_back(lightmapper->get_bake_texture(i));
 		images.push_back(lightmapper->get_bake_texture(i));
 	}
 	}
 
 
+	bool use_srgb = use_color && !use_hdr;
+
 	if (gen_atlas) {
 	if (gen_atlas) {
 
 
 		Ref<Image> large_image;
 		Ref<Image> large_image;
@@ -906,13 +957,7 @@ BakedLightmap::BakeError BakedLightmap::bake(Node *p_from_node, String p_data_sa
 		String base_path = p_data_save_path.get_basename();
 		String base_path = p_data_save_path.get_basename();
 
 
 		if (ResourceLoader::import) {
 		if (ResourceLoader::import) {
-
-			base_path += ".exr";
-			String relative_path = base_path;
-			if (relative_path.begins_with("res://")) {
-				relative_path = relative_path.substr(6, relative_path.length());
-			}
-			large_image->save_exr(relative_path, false);
+			_save_image(base_path, large_image, use_srgb);
 
 
 			Ref<ConfigFile> config;
 			Ref<ConfigFile> config;
 			config.instance();
 			config.instance();
@@ -928,7 +973,7 @@ BakedLightmap::BakeError BakedLightmap::bake(Node *p_from_node, String p_data_sa
 			config->set_value("params", "flags/repeat", false);
 			config->set_value("params", "flags/repeat", false);
 			config->set_value("params", "flags/filter", true);
 			config->set_value("params", "flags/filter", true);
 			config->set_value("params", "flags/mipmaps", false);
 			config->set_value("params", "flags/mipmaps", false);
-			config->set_value("params", "flags/srgb", false);
+			config->set_value("params", "flags/srgb", use_srgb);
 			config->set_value("params", "slices/horizontal", 1);
 			config->set_value("params", "slices/horizontal", 1);
 			config->set_value("params", "slices/vertical", images.size());
 			config->set_value("params", "slices/vertical", images.size());
 			config->save(base_path + ".import");
 			config->save(base_path + ".import");
@@ -987,12 +1032,7 @@ BakedLightmap::BakeError BakedLightmap::bake(Node *p_from_node, String p_data_sa
 
 
 			if (ResourceLoader::import) {
 			if (ResourceLoader::import) {
 
 
-				base_path += ".exr";
-				String relative_path = base_path;
-				if (relative_path.begins_with("res://")) {
-					relative_path = relative_path.substr(6, relative_path.length());
-				}
-				images[i]->save_exr(relative_path, false);
+				_save_image(base_path, images[i], use_srgb);
 
 
 				Ref<ConfigFile> config;
 				Ref<ConfigFile> config;
 				config.instance();
 				config.instance();
@@ -1008,7 +1048,7 @@ BakedLightmap::BakeError BakedLightmap::bake(Node *p_from_node, String p_data_sa
 				config->set_value("params", "flags/repeat", false);
 				config->set_value("params", "flags/repeat", false);
 				config->set_value("params", "flags/filter", true);
 				config->set_value("params", "flags/filter", true);
 				config->set_value("params", "flags/mipmaps", false);
 				config->set_value("params", "flags/mipmaps", false);
-				config->set_value("params", "flags/srgb", false);
+				config->set_value("params", "flags/srgb", use_srgb);
 
 
 				config->save(base_path + ".import");
 				config->save(base_path + ".import");
 
 
@@ -1309,6 +1349,26 @@ bool BakedLightmap::is_using_denoiser() const {
 	return use_denoiser;
 	return use_denoiser;
 }
 }
 
 
+void BakedLightmap::set_use_hdr(bool p_enable) {
+
+	use_hdr = p_enable;
+}
+
+bool BakedLightmap::is_using_hdr() const {
+
+	return use_hdr;
+}
+
+void BakedLightmap::set_use_color(bool p_enable) {
+
+	use_color = p_enable;
+}
+
+bool BakedLightmap::is_using_color() const {
+
+	return use_color;
+}
+
 void BakedLightmap::set_environment_mode(EnvironmentMode p_mode) {
 void BakedLightmap::set_environment_mode(EnvironmentMode p_mode) {
 	environment_mode = p_mode;
 	environment_mode = p_mode;
 	_change_notify();
 	_change_notify();
@@ -1428,6 +1488,12 @@ void BakedLightmap::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("set_use_denoiser", "use_denoiser"), &BakedLightmap::set_use_denoiser);
 	ClassDB::bind_method(D_METHOD("set_use_denoiser", "use_denoiser"), &BakedLightmap::set_use_denoiser);
 	ClassDB::bind_method(D_METHOD("is_using_denoiser"), &BakedLightmap::is_using_denoiser);
 	ClassDB::bind_method(D_METHOD("is_using_denoiser"), &BakedLightmap::is_using_denoiser);
 
 
+	ClassDB::bind_method(D_METHOD("set_use_hdr", "use_denoiser"), &BakedLightmap::set_use_hdr);
+	ClassDB::bind_method(D_METHOD("is_using_hdr"), &BakedLightmap::is_using_hdr);
+
+	ClassDB::bind_method(D_METHOD("set_use_color", "use_denoiser"), &BakedLightmap::set_use_color);
+	ClassDB::bind_method(D_METHOD("is_using_color"), &BakedLightmap::is_using_color);
+
 	ClassDB::bind_method(D_METHOD("set_generate_atlas", "enabled"), &BakedLightmap::set_generate_atlas);
 	ClassDB::bind_method(D_METHOD("set_generate_atlas", "enabled"), &BakedLightmap::set_generate_atlas);
 	ClassDB::bind_method(D_METHOD("is_generate_atlas_enabled"), &BakedLightmap::is_generate_atlas_enabled);
 	ClassDB::bind_method(D_METHOD("is_generate_atlas_enabled"), &BakedLightmap::is_generate_atlas_enabled);
 
 
@@ -1463,6 +1529,8 @@ void BakedLightmap::_bind_methods() {
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "quality", PROPERTY_HINT_ENUM, "Low,Medium,High,Ultra"), "set_bake_quality", "get_bake_quality");
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "quality", PROPERTY_HINT_ENUM, "Low,Medium,High,Ultra"), "set_bake_quality", "get_bake_quality");
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "bounces", PROPERTY_HINT_RANGE, "0,16,1"), "set_bounces", "get_bounces");
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "bounces", PROPERTY_HINT_RANGE, "0,16,1"), "set_bounces", "get_bounces");
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_denoiser"), "set_use_denoiser", "is_using_denoiser");
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_denoiser"), "set_use_denoiser", "is_using_denoiser");
+	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_hdr"), "set_use_hdr", "is_using_hdr");
+	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_color"), "set_use_color", "is_using_color");
 	ADD_PROPERTY(PropertyInfo(Variant::REAL, "bias", PROPERTY_HINT_RANGE, "0.00001,0.1,0.00001,or_greater"), "set_bias", "get_bias");
 	ADD_PROPERTY(PropertyInfo(Variant::REAL, "bias", PROPERTY_HINT_RANGE, "0.00001,0.1,0.00001,or_greater"), "set_bias", "get_bias");
 	ADD_PROPERTY(PropertyInfo(Variant::REAL, "default_texels_per_unit", PROPERTY_HINT_RANGE, "0.0,64.0,0.01,or_greater"), "set_default_texels_per_unit", "get_default_texels_per_unit");
 	ADD_PROPERTY(PropertyInfo(Variant::REAL, "default_texels_per_unit", PROPERTY_HINT_RANGE, "0.0,64.0,0.01,or_greater"), "set_default_texels_per_unit", "get_default_texels_per_unit");
 
 
@@ -1528,6 +1596,8 @@ BakedLightmap::BakedLightmap() {
 	environment_custom_energy = 1.0;
 	environment_custom_energy = 1.0;
 
 
 	use_denoiser = true;
 	use_denoiser = true;
+	use_hdr = true;
+	use_color = true;
 	bias = 0.005;
 	bias = 0.005;
 
 
 	generate_atlas = true;
 	generate_atlas = true;

+ 9 - 0
scene/3d/baked_lightmap.h

@@ -161,6 +161,8 @@ private:
 	bool capture_enabled;
 	bool capture_enabled;
 	int bounces;
 	int bounces;
 	bool use_denoiser;
 	bool use_denoiser;
+	bool use_hdr;
+	bool use_color;
 
 
 	EnvironmentMode environment_mode;
 	EnvironmentMode environment_mode;
 	Ref<Sky> environment_custom_sky;
 	Ref<Sky> environment_custom_sky;
@@ -186,6 +188,7 @@ private:
 	Vector2i _compute_lightmap_size(const MeshesFound &p_mesh);
 	Vector2i _compute_lightmap_size(const MeshesFound &p_mesh);
 
 
 	static bool _lightmap_bake_step_function(float p_completion, const String &p_text, void *ud, bool p_refresh);
 	static bool _lightmap_bake_step_function(float p_completion, const String &p_text, void *ud, bool p_refresh);
+	void _save_image(String &r_base_path, Ref<Image> p_img, bool p_use_srgb);
 
 
 protected:
 protected:
 	static void _bind_methods();
 	static void _bind_methods();
@@ -247,6 +250,12 @@ public:
 	void set_use_denoiser(bool p_enable);
 	void set_use_denoiser(bool p_enable);
 	bool is_using_denoiser() const;
 	bool is_using_denoiser() const;
 
 
+	void set_use_hdr(bool p_enable);
+	bool is_using_hdr() const;
+
+	void set_use_color(bool p_enable);
+	bool is_using_color() const;
+
 	void set_bounces(int p_bounces);
 	void set_bounces(int p_bounces);
 	int get_bounces() const;
 	int get_bounces() const;