Repository: godotengine/godot (language: cpp)

Issue:
CurveTexture doesn't render properly on iOS Safari
### Tested versions

- Reproducible on master (e5498020b6250d9284efcdcf0f85f39d48d8548f)

### System information

Godot v4.4.beta (e5498020b) - macOS Sequoia (15.2.0) - Multi-window, 1 monitor - OpenGL 3 (Compatibility) - Apple M3 Max - Apple M3 Max (16 threads)

### Issue description

Rendering a CurveTexture on standard platforms reveal no issues.

<img width="381" alt="Image" src="https://github.com/user-attachments/assets/c042e0a8-5e19-46f7-8c24-afeabd33c316" />

But rendering a CurveTexture on iOS Safari results in a black image.

![Image](https://github.com/user-attachments/assets/364a0cee-001e-4077-9906-2a7ed77903a6)

I printed the contents of the texture data, and it doesn't seem awry. Later testing revealed that it's an image format issue.

### Steps to reproduce

1. Create a TextureRect. (and ideally set a minimal size like 300x300)
2. Use a CurveTexture inside it.
3. Setup the curve of that curve texture until you have a black and white texture
4. Test on the desktop, see that the image is displayed
5. Test on an iPad, see that the image is black

### Minimal reproduction project (MRP)

[curve_texture_test_2025-01-27_09-23-32.zip](https://github.com/user-attachments/files/18559398/curve_texture_test_2025-01-27_09-23-32.zip)


Discussion / hints:

Fix patch:
diff --git a/drivers/gles3/storage/config.cpp b/drivers/gles3/storage/config.cpp
index fb45fdeaca7b..8e1d08274f4f 100644
--- a/drivers/gles3/storage/config.cpp
+++ b/drivers/gles3/storage/config.cpp
@@ -84,12 +84,14 @@ Config::Config() {
 
 	if (RasterizerGLES3::is_gles_over_gl()) {
 		float_texture_supported = true;
+		float_texture_linear_supported = true;
 		etc2_supported = false;
 		s3tc_supported = true;
 		rgtc_supported = true; //RGTC - core since OpenGL version 3.0
 		srgb_framebuffer_supported = true;
 	} else {
 		float_texture_supported = extensions.has("GL_EXT_color_buffer_float");
+		float_texture_linear_supported = extensions.has("GL_OES_texture_float_linear");
 		etc2_supported = true;
 #if defined(ANDROID_ENABLED) || defined(IOS_ENABLED)
 		// Some Android devices report support for S3TC but we don't expect that and don't export the textures.
diff --git a/drivers/gles3/storage/config.h b/drivers/gles3/storage/config.h
index e4dfe6a35ee9..36fae084b0e6 100644
--- a/drivers/gles3/storage/config.h
+++ b/drivers/gles3/storage/config.h
@@ -72,6 +72,7 @@ class Config {
 	HashSet<String> extensions;
 
 	bool float_texture_supported = false;
+	bool float_texture_linear_supported = false;
 	bool s3tc_supported = false;
 	bool rgtc_supported = false;
 	bool bptc_supported = false;
diff --git a/drivers/gles3/storage/texture_storage.cpp b/drivers/gles3/storage/texture_storage.cpp
index c1b2784bfc05..de5938176857 100644
--- a/drivers/gles3/storage/texture_storage.cpp
+++ b/drivers/gles3/storage/texture_storage.cpp
@@ -334,7 +334,9 @@ void TextureStorage::canvas_texture_set_texture_repeat(RID p_canvas_texture, RS:
 
 /* Texture API */
 
-static inline Error _get_gl_uncompressed_format(Image::Format p_format, GLenum &r_gl_format, GLenum &r_gl_internal_format, GLenum &r_gl_type) {
+static inline Error _get_gl_uncompressed_format(const Ref<Image> &p_image, Image::Format p_format, Image::Format &r_real_format, GLenum &r_gl_format, GLenum &r_gl_internal_format, GLenum &r_gl_type) {
+	Config *config = Config::get_singleton();
+
 	switch (p_format) {
 		case Image::FORMAT_L8: {
 			if (RasterizerGLES3::is_gles_over_gl()) {
@@ -389,24 +391,68 @@ static inline Error _get_gl_uncompressed_format(Image::Format p_format, GLenum &
 			r_gl_type = GL_UNSIGNED_SHORT_5_6_5;
 		} break;
 		case Image::FORMAT_RF: {
-			r_gl_internal_format = GL_R32F;
-			r_gl_format = GL_RED;
-			r_gl_type = GL_FLOAT;
+			if (config->float_texture_linear_supported) {
+				r_gl_internal_format = GL_R32F;
+				r_gl_format = GL_RED;
+				r_gl_type = GL_FLOAT;
+			} else {
+				ERR_PRINT("R32 float texture not supported, converting to R16.");
+				if (p_image.is_valid()) {
+					p_image->convert(Image::FORMAT_RH);
+				}
+				r_real_format = Image::FORMAT_RH;
+				r_gl_internal_format = GL_R16F;
+				r_gl_format = GL_RED;
+				r_gl_type = GL_HALF_FLOAT;
+			}
 		} break;
 		case Image::FORMAT_RGF: {
-			r_gl_internal_format = GL_RG32F;
-			r_gl_format = GL_RG;
-			r_gl_type = GL_FLOAT;
+			if (config->float_texture_linear_supported) {
+				r_gl_internal_format = GL_RG32F;
+				r_gl_format = GL_RG;
+				r_gl_type = GL_FLOAT;
+			} else {
+				ERR_PRINT("RG32 float texture not supported, converting to RG16.");
+				if (p_image.is_valid()) {
+					p_image->convert(Image::FORMAT_RGH);
+				}
+				r_real_format = Image::FORMAT_RGH;
+				r_gl_internal_format = GL_RG16F;
+				r_gl_format = GL_RG;
+				r_gl_type = GL_HALF_FLOAT;
+			}
 		} break;
 		case Image::FORMAT_RGBF: {
-			r_gl_internal_format = GL_RGB32F;
-			r_gl_format = GL_RGB;
-			r_gl_type = GL_FLOAT;
+			if (config->float_texture_linear_supported) {
+				r_gl_internal_format = GL_RGB32F;
+				r_gl_format = GL_RGB;
+				r_gl_type = GL_FLOAT;
+			} else {
+				ERR_PRINT("RGB32 float texture not supported, converting to RGB16.");
+				if (p_image.is_valid()) {
+					p_image->convert(Image::FORMAT_RGBH);
+				}
+				r_real_format = Image::FORMAT_RGBH;
+				r_gl_internal_format = GL_RGB16F;
+				r_gl_format = GL_RGB;
+				r_gl_type = GL_HALF_FLOAT;
+			}
 		} break;
 		case Image::FORMAT_RGBAF: {
-			r_gl_internal_format = GL_RGBA32F;
-			r_gl_format = GL_RGBA;
-			r_gl_type = GL_FLOAT;
+			if (config->float_texture_linear_supported) {
+				r_gl_internal_format = GL_RGBA32F;
+				r_gl_format = GL_RGBA;
+				r_gl_type = GL_FLOAT;
+			} else {
+				ERR_PRINT("RGBA32 float texture not supported, converting to RGBA16.");
+				if (p_image.is_valid()) {
+					p_image->convert(Image::FORMAT_RGBAH);
+				}
+				r_real_format = Image::FORMAT_RGBAH;
+				r_gl_internal_format = GL_RGBA16F;
+				r_gl_format = GL_RGBA;
+				r_gl_type = GL_HALF_FLOAT;
+			}
 		} break;
 		case Image::FORMAT_RH: {
 			r_gl_internal_format = GL_R16F;
@@ -449,7 +495,7 @@ Ref<Image> TextureStorage::_get_gl_image_and_format(const Ref<Image> &p_image, I
 	r_real_format = p_format;
 
 	if (!Image::is_format_compressed(p_format)) {
-		Error err = _get_gl_uncompressed_format(p_format, r_gl_format, r_gl_internal_format, r_gl_type);
+		Error err = _get_gl_uncompressed_format(p_image, p_format, r_real_format, r_gl_format, r_gl_internal_format, r_gl_type);
 		ERR_FAIL_COND_V_MSG(err != OK, Ref<Image>(), vformat("The image format %d is not supported by the Compatibility renderer.", p_format));
 		return p_image;
 	}
@@ -694,7 +740,7 @@ Ref<Image> TextureStorage::_get_gl_image_and_format(const Ref<Image> &p_image, I
 				image->convert(Image::FORMAT_RG8);
 			}
 
-			Error err = _get_gl_uncompressed_format(image->get_format(), r_gl_format, r_gl_internal_format, r_gl_type);
+			Error err = _get_gl_uncompressed_format(image, image->get_format(), r_real_format, r_gl_format, r_gl_internal_format, r_gl_type);
 			ERR_FAIL_COND_V_MSG(err != OK, Ref<Image>(), vformat("The image format %d is not supported by the Compatibility renderer.", image->get_format()));
 
 			r_real_format = image->get_format();


Test patch: