Commits

Alex Szpakowski  committed 5723b68 Merge

Merged bartbes/love-experiments/Canvas-formats into default

  • Participants
  • Parent commits c679dab, f9f9e1a

Comments (0)

Files changed (11)

File src/modules/graphics/Texture.cpp

 	return wrapModes.find(in, out);
 }
 
-bool Texture::getConstant(const char *in, Format &out)
-{
-	return formats.find(in, out);
-}
-
-bool Texture::getConstant(Format in, const char *&out)
-{
-	return formats.find(in, out);
-}
-
 StringMap<Texture::FilterMode, Texture::FILTER_MAX_ENUM>::Entry Texture::filterModeEntries[] =
 {
 	{ "linear", Texture::FILTER_LINEAR },
 
 StringMap<Texture::WrapMode, Texture::WRAP_MAX_ENUM> Texture::wrapModes(Texture::wrapModeEntries, sizeof(Texture::wrapModeEntries));
 
-StringMap<Texture::Format, Texture::FORMAT_MAX_ENUM>::Entry Texture::formatEntries[] =
-{
-	{"normal", Texture::FORMAT_NORMAL},
-	{"hdr", Texture::FORMAT_HDR},
-	{"srgb", Texture::FORMAT_SRGB},
-};
-
-StringMap<Texture::Format, Texture::FORMAT_MAX_ENUM> Texture::formats(Texture::formatEntries, sizeof(Texture::formatEntries));
-
-
 } // graphics
 } // love

File src/modules/graphics/Texture.h

 		FILTER_MAX_ENUM
 	};
 
-	enum Format
-	{
-		FORMAT_NORMAL,
-		FORMAT_HDR,
-		FORMAT_SRGB,
-		FORMAT_MAX_ENUM
-	};
-
 	struct Filter
 	{
 		Filter();
 	static bool getConstant(const char *in, WrapMode &out);
 	static bool getConstant(WrapMode in, const char  *&out);
 
-	static bool getConstant(const char *in, Format &out);
-	static bool getConstant(Format in, const char *&out);
-
 protected:
 
 	int width;
 	static StringMap<WrapMode, WRAP_MAX_ENUM>::Entry wrapModeEntries[];
 	static StringMap<WrapMode, WRAP_MAX_ENUM> wrapModes;
 
-	static StringMap<Format, FORMAT_MAX_ENUM>::Entry formatEntries[];
-	static StringMap<Format, FORMAT_MAX_ENUM> formats;
-
 }; // Texture
 
 } // graphics

File src/modules/graphics/opengl/Canvas.cpp

 	}
 }
 
-Canvas::Canvas(int width, int height, Texture::Format format, int fsaa)
+Canvas::Canvas(int width, int height, Format format, int fsaa)
 	: fbo(0)
     , resolve_fbo(0)
 	, texture(0)
 	setFilter(filter);
 	setWrap(wrap);
 
-	GLint internalformat;
-	GLenum textype;
-	switch (format)
-	{
-	case Texture::FORMAT_HDR:
-		internalformat = GL_RGBA16F;
-		textype = GL_FLOAT;
-		break;
-	case Texture::FORMAT_SRGB:
-		internalformat = GL_SRGB8_ALPHA8;
-		textype = GL_UNSIGNED_BYTE;
-		break;
-	case Texture::FORMAT_NORMAL:
-	default:
-		internalformat = GL_RGBA8;
-		textype = GL_UNSIGNED_BYTE;
-	}
+	GLenum internalformat = GL_RGBA;
+	GLenum externalformat = GL_RGBA;
+	GLenum textype = GL_UNSIGNED_BYTE;
+
+	convertFormat(format, internalformat, externalformat, textype);
 
 	while (glGetError() != GL_NO_ERROR)
 		/* Clear the error buffer. */;
 
 	glTexImage2D(GL_TEXTURE_2D,
 	             0,
-	             internalformat,
+	             (GLint) internalformat,
 	             width, height,
 	             0,
-	             GL_RGBA,
+	             externalformat,
 	             textype,
 	             nullptr);
 
 	return true;
 }
 
+Canvas::Format Canvas::getSizedFormat(Canvas::Format format)
+{
+	switch (format)
+	{
+	case FORMAT_NORMAL:
+		return FORMAT_RGBA8;
+	case FORMAT_HDR:
+		return FORMAT_RGBA16F;
+	default:
+		return format;
+	}
+}
+
+void Canvas::convertFormat(Canvas::Format format, GLenum &internalformat, GLenum &externalformat, GLenum &type)
+{
+	format = getSizedFormat(format);
+	externalformat = GL_RGBA;
+
+	switch (format)
+	{
+	case FORMAT_RGBA8:
+	default:
+		internalformat = GL_RGBA8;
+		type = GL_UNSIGNED_BYTE;
+		break;
+	case FORMAT_RGBA4:
+		internalformat = GL_RGBA4;
+		type = GL_UNSIGNED_SHORT_4_4_4_4;
+		break;
+	case FORMAT_RGB5A1:
+		internalformat = GL_RGB5_A1;
+		type = GL_UNSIGNED_SHORT_5_5_5_1;
+		break;
+	case FORMAT_RGB565:
+		internalformat = GL_RGB565;
+		externalformat = GL_RGB;
+		type = GL_UNSIGNED_SHORT_5_6_5;
+		break;
+	case FORMAT_RGB10A2:
+		internalformat = GL_RGB10_A2;
+		type = GL_UNSIGNED_INT_10_10_10_2;
+		break;
+	case FORMAT_RG11B10F:
+		internalformat = GL_R11F_G11F_B10F;
+		externalformat = GL_RGB;
+		type = GL_UNSIGNED_INT_10F_11F_11F_REV;
+		break;
+	case FORMAT_RGBA16F:
+		internalformat = GL_RGBA16F;
+		type = GL_FLOAT;
+		break;
+	case FORMAT_RGBA32F:
+		internalformat = GL_RGBA32F;
+		type = GL_FLOAT;
+		break;
+	case FORMAT_SRGB:
+		internalformat = GL_SRGB8_ALPHA8;
+		type = GL_UNSIGNED_BYTE;
+		break;
+	}
+}
+
 bool Canvas::isSupported()
 {
 	getStrategy();
 	return (strategy != &strategyNone);
 }
 
-bool Canvas::isHDRSupported()
-{
-	return GLEE_VERSION_3_0 || (isSupported() && GLEE_ARB_texture_float);
-}
-
-bool Canvas::isSRGBSupported()
-{
-	if (GLEE_VERSION_3_0)
-		return true;
-
-	if (!isSupported())
-		return false;
-
-	return (GLEE_ARB_framebuffer_sRGB || GLEE_EXT_framebuffer_sRGB)
-		&& GLEE_EXT_texture_sRGB;
-}
-
 bool Canvas::isMultiCanvasSupported()
 {
 	// system must support at least 4 simultanious active canvases.
 	return gl.getMaxRenderTargets() >= 4;
 }
 
+bool Canvas::supportedFormats[] = {false};
+bool Canvas::checkedFormats[] = {false};
+
+bool Canvas::isFormatSupported(Canvas::Format format)
+{
+	if (!isSupported())
+		return false;
+
+	bool supported = true;
+	format = getSizedFormat(format);
+
+	switch (format)
+	{
+	case FORMAT_RGBA8:
+	case FORMAT_RGBA4:
+	case FORMAT_RGB5A1:
+	case FORMAT_RGB10A2:
+		supported = true;
+		break;
+	case FORMAT_RGB565:
+		supported = GLEE_VERSION_4_2 || GLEE_ARB_ES2_compatibility;
+		break;
+	case FORMAT_RG11B10F:
+		supported = GLEE_VERSION_3_0 || (GLEE_ARB_texture_float && GLEE_EXT_packed_float);
+		break;
+	case FORMAT_RGBA16F:
+	case FORMAT_RGBA32F:
+		supported = GLEE_VERSION_3_0 || GLEE_ARB_texture_float;
+		break;
+	case FORMAT_SRGB:
+		supported = GLEE_VERSION_3_0 || ((GLEE_ARB_framebuffer_sRGB || GLEE_EXT_framebuffer_sRGB)
+			&& (GLEE_VERSION_2_1 || GLEE_EXT_texture_sRGB));
+		break;
+	default:
+		supported = false;
+		break;
+	}
+
+	if (!supported)
+		return false;
+
+	if (checkedFormats[format])
+		return supportedFormats[format];
+
+	// Even though we might have the necessary OpenGL version or extension,
+	// drivers are still allowed to throw FRAMEBUFFER_UNSUPPORTED when attaching
+	// a texture to a FBO whose format the driver doesn't like. So we should
+	// test with an actual FBO.
+
+	GLenum internalformat = GL_RGBA;
+	GLenum externalformat = GL_RGBA;
+	GLenum textype = GL_UNSIGNED_BYTE;
+	convertFormat(format, internalformat, externalformat, textype);
+
+	GLuint texture = 0;
+	glGenTextures(1, &texture);
+	gl.bindTexture(texture);
+
+	Texture::Filter f;
+	f.min = f.mag = Texture::FILTER_NEAREST;
+	gl.setTextureFilter(f);
+
+	Texture::Wrap w;
+	gl.setTextureWrap(w);
+
+	glTexImage2D(GL_TEXTURE_2D, 0, internalformat, 2, 2, 0, externalformat, textype, nullptr);
+
+	GLuint fbo = 0;
+	supported = (strategy->createFBO(fbo, texture) == GL_FRAMEBUFFER_COMPLETE);
+	strategy->deleteFBO(fbo, 0, 0);
+
+	gl.deleteTexture(texture);
+
+	// Cache the result so we don't do this for every isFormatSupported call.
+	checkedFormats[format] = true;
+	supportedFormats[format] = supported;
+
+	return supported;
+}
+
 void Canvas::bindDefaultCanvas()
 {
 	if (current != nullptr)
 		current->stopGrab();
 }
 
+bool Canvas::getConstant(const char *in, Format &out)
+{
+	return formats.find(in, out);
+}
+
+bool Canvas::getConstant(Format in, const char *&out)
+{
+	return formats.find(in, out);
+}
+
+StringMap<Canvas::Format, Canvas::FORMAT_MAX_ENUM>::Entry Canvas::formatEntries[] =
+{
+	{"normal", Canvas::FORMAT_NORMAL},
+	{"hdr", Canvas::FORMAT_HDR},
+	{"rgba8", Canvas::FORMAT_RGBA8},
+	{"rgba4", Canvas::FORMAT_RGBA4},
+	{"rgb5a1", Canvas::FORMAT_RGB5A1},
+	{"rgb565", Canvas::FORMAT_RGB565},
+	{"rgb10a2", Canvas::FORMAT_RGB10A2},
+	{"rg11b10f", Canvas::FORMAT_RG11B10F},
+	{"rgba16f", Canvas::FORMAT_RGBA16F},
+	{"rgba32f", Canvas::FORMAT_RGBA32F},
+	{"srgb", Canvas::FORMAT_SRGB},
+};
+
+StringMap<Canvas::Format, Canvas::FORMAT_MAX_ENUM> Canvas::formats(Canvas::formatEntries, sizeof(Canvas::formatEntries));
+
 } // opengl
 } // graphics
 } // love

File src/modules/graphics/opengl/Canvas.h

 #include "image/Image.h"
 #include "image/ImageData.h"
 #include "common/Matrix.h"
+#include "common/StringMap.h"
 #include "Texture.h"
 #include "OpenGL.h"
 
 {
 public:
 
-	Canvas(int width, int height, Texture::Format format = Texture::FORMAT_NORMAL, int fsaa = 0);
+	// Different Canvas render target formats.
+	enum Format
+	{
+		FORMAT_NORMAL,   // Usually RGBA8 or a similar fallback. Always supported.
+		FORMAT_HDR,      // Usually RGBA16F. Not always supported.
+		FORMAT_RGBA8,    // RGBA with 8 bits per component.
+		FORMAT_RGBA4,    // RGBA with 4 bits per component.
+		FORMAT_RGB5A1,   // RGB with 5 bits per component, and A with 1 bit.
+		FORMAT_RGB565,   // RGB with 5, 6, and 5 bits each, respectively.
+		FORMAT_RGB10A2,  // RGB with 10 bits each, and A with 2 bits.
+		FORMAT_RG11B10F, // Floating point [0, +inf]. RG with 11 FP bits each, and B with 10 FP bits.
+		FORMAT_RGBA16F,  // Floating point [-inf, +inf]. RGBA with 16 FP bits per component.
+		FORMAT_RGBA32F,  // Floating point [-inf, +inf]. RGBA with 32 FP bits per component.
+		FORMAT_SRGB,     // sRGB with 8 bits per component, plus 8 bit linear A.
+		FORMAT_MAX_ENUM
+	};
+
+	Canvas(int width, int height, Format format = FORMAT_NORMAL, int fsaa = 0);
 	virtual ~Canvas();
 
 	// Implements Volatile.
 		return status;
 	}
 
-	inline Texture::Format getTextureFormat() const
+	inline Format getTextureFormat() const
 	{
 		return format;
 	}
 	bool resolveMSAA();
 
 	static bool isSupported();
-	static bool isHDRSupported();
-	static bool isSRGBSupported();
 	static bool isMultiCanvasSupported();
+	static bool isFormatSupported(Format format);
 
 	static Canvas *current;
 	static void bindDefaultCanvas();
 	// Whether the main screen should have linear -> sRGB conversions enabled.
 	static bool screenHasSRGB;
 
+	static bool getConstant(const char *in, Format &out);
+	static bool getConstant(Format in, const char *&out);
+
 private:
 
 	bool createFSAAFBO(GLenum internalformat);
 
+	static Format getSizedFormat(Format format);
+	static void convertFormat(Format format, GLenum &internalformat, GLenum &externalformat, GLenum &type);
+
 	GLuint fbo;
 	GLuint resolve_fbo;
 
 	void setupGrab();
 	void drawv(const Matrix &t, const Vertex *v);
 
+	static bool supportedFormats[FORMAT_MAX_ENUM];
+	static bool checkedFormats[FORMAT_MAX_ENUM];
+
+	static StringMap<Format, FORMAT_MAX_ENUM>::Entry formatEntries[];
+	static StringMap<Format, FORMAT_MAX_ENUM> formats;
+
 }; // Canvas
 
 } // opengl

File src/modules/graphics/opengl/Graphics.cpp

 	activeStencil = false;
 }
 
-Image *Graphics::newImage(love::image::ImageData *data, Texture::Format format)
+Image *Graphics::newImage(love::image::ImageData *data, Image::Format format)
 {
 	// Create the image.
 	Image *image = new Image(data, format);
 	return image;
 }
 
-Image *Graphics::newImage(love::image::CompressedData *cdata, Texture::Format format)
+Image *Graphics::newImage(love::image::CompressedData *cdata, Image::Format format)
 {
 	// Create the image.
 	Image *image = new Image(cdata, format);
 	return new ParticleSystem(texture, size);
 }
 
-Canvas *Graphics::newCanvas(int width, int height, Texture::Format format, int fsaa)
+Canvas *Graphics::newCanvas(int width, int height, Canvas::Format format, int fsaa)
 {
-	if (format == Texture::FORMAT_HDR && !Canvas::isHDRSupported())
-		throw Exception("HDR Canvases are not supported by your OpenGL implementation");
-
-	if (format == Texture::FORMAT_SRGB && !Canvas::isSRGBSupported())
-		throw Exception("sRGB Canvases are not supported by your OpenGL implementation");
+	if (!Canvas::isFormatSupported(format))
+	{
+		const char *fstr = "rgba8";
+		Canvas::getConstant(format, fstr);
+		throw love::Exception("The %s canvas format is not supported by your OpenGL implementation.", fstr);
+	}
 
 	if (width > gl.getMaxTextureSize())
 		throw Exception("Cannot create canvas: width of %d pixels is too large for this system.", width);
 	}
 
 	canvas->release();
-	throw Exception(error_string.str().c_str());
-	return NULL; // never reached
+	throw Exception("%s", error_string.str().c_str());
+	return nullptr; // never reached
 }
 
 Shader *Graphics::newShader(const Shader::ShaderSources &sources)

File src/modules/graphics/opengl/Graphics.h

 	/**
 	 * Creates an Image object with padding and/or optimization.
 	 **/
-	Image *newImage(love::image::ImageData *data, Texture::Format format = Texture::FORMAT_NORMAL);
-	Image *newImage(love::image::CompressedData *cdata, Texture::Format format = Texture::FORMAT_NORMAL);
+	Image *newImage(love::image::ImageData *data, Image::Format format = Image::FORMAT_NORMAL);
+	Image *newImage(love::image::CompressedData *cdata, Image::Format format = Image::FORMAT_NORMAL);
 
 	Quad *newQuad(Quad::Viewport v, float sw, float sh);
 
 
 	ParticleSystem *newParticleSystem(Texture *texture, int size);
 
-	Canvas *newCanvas(int width, int height, Texture::Format format = Texture::FORMAT_NORMAL, int fsaa = 0);
+	Canvas *newCanvas(int width, int height, Canvas::Format format = Canvas::FORMAT_NORMAL, int fsaa = 0);
 
 	Shader *newShader(const Shader::ShaderSources &sources);
 

File src/modules/graphics/opengl/Image.cpp

 Texture::FilterMode Image::defaultMipmapFilter = Texture::FILTER_NONE;
 float Image::defaultMipmapSharpness = 0.0f;
 
-Image::Image(love::image::ImageData *data, Texture::Format format)
+Image::Image(love::image::ImageData *data, Format format)
 	: data(data)
 	, cdata(nullptr)
 	, paddedWidth(width)
 	preload();
 }
 
-Image::Image(love::image::CompressedData *cdata, Texture::Format format)
+Image::Image(love::image::CompressedData *cdata, Format format)
 	: data(nullptr)
 	, cdata(cdata)
 	, paddedWidth(width)
 	return true;
 }
 
-Texture::Format Image::getFormat() const
+Image::Format Image::getFormat() const
 {
 	return format;
 }
 	return GLEE_VERSION_2_1 || GLEE_EXT_texture_sRGB;
 }
 
+bool Image::getConstant(const char *in, Format &out)
+{
+	return formats.find(in, out);
+}
+
+bool Image::getConstant(Format in, const char *&out)
+{
+	return formats.find(in, out);
+}
+
+StringMap<Image::Format, Image::FORMAT_MAX_ENUM>::Entry Image::formatEntries[] =
+{
+	{"normal", Image::FORMAT_NORMAL},
+	{"srgb", Image::FORMAT_SRGB},
+};
+
+StringMap<Image::Format, Image::FORMAT_MAX_ENUM> Image::formats(Image::formatEntries, sizeof(Image::formatEntries));
+
 } // opengl
 } // graphics
 } // love

File src/modules/graphics/opengl/Image.h

 #include "common/config.h"
 #include "common/Matrix.h"
 #include "common/Vector.h"
+#include "common/StringMap.h"
 #include "common/math.h"
 #include "image/ImageData.h"
 #include "image/CompressedData.h"
 {
 public:
 
+	enum Format
+	{
+		FORMAT_NORMAL,
+		FORMAT_SRGB,
+		FORMAT_MAX_ENUM
+	};
+
 	/**
 	 * Creates a new Image. Not that anything is ready to use
 	 * before load is called.
 	 *
 	 * @param data The data from which to load the image.
 	 **/
-	Image(love::image::ImageData *data, Texture::Format format = Texture::FORMAT_NORMAL);
+	Image(love::image::ImageData *data, Format format = FORMAT_NORMAL);
 
 	/**
 	 * Creates a new Image with compressed image data.
 	 *
 	 * @param cdata The compressed data from which to load the image.
 	 **/
-	Image(love::image::CompressedData *cdata, Texture::Format format = Texture::FORMAT_NORMAL);
+	Image(love::image::CompressedData *cdata, Format format = FORMAT_NORMAL);
 
 	/**
 	 * Destructor. Deletes the hardware texture and other resources.
 	 **/
 	bool refresh();
 
-	Texture::Format getFormat() const;
+	Format getFormat() const;
 
 	static void setDefaultMipmapSharpness(float sharpness);
 	static float getDefaultMipmapSharpness();
 
 	static bool hasSRGBSupport();
 
+	static bool getConstant(const char *in, Format &out);
+	static bool getConstant(Format in, const char *&out);
+
 private:
 
 	void uploadDefaultTexture();
 	// Whether this Image is using a compressed texture.
 	bool compressed;
 
-	// The format to interpret the texture's data as.
-	Texture::Format format;
+	// The format to interpret the image's data as.
+	Format format;
 
 	// True if the image wasn't able to be properly created and it had to fall
 	// back to a default texture.
 
 	GLenum getCompressedFormat(image::CompressedData::Format cformat) const;
 
+	static StringMap<Format, FORMAT_MAX_ENUM>::Entry formatEntries[];
+	static StringMap<Format, FORMAT_MAX_ENUM> formats;
+
 }; // Image
 
 } // opengl

File src/modules/graphics/opengl/wrap_Canvas.cpp

 int w_Canvas_getFormat(lua_State *L)
 {
 	Canvas *canvas = luax_checkcanvas(L, 1);
-	Texture::Format format = canvas->getTextureFormat();
+	Canvas::Format format = canvas->getTextureFormat();
 	const char *str;
-	if (!Texture::getConstant(format, str))
-		return luaL_error(L, "Unknown texture format.");
+	if (!Canvas::getConstant(format, str))
+		return luaL_error(L, "Unknown Canvas format.");
 
 	lua_pushstring(L, str);
 	return 1;

File src/modules/graphics/opengl/wrap_Graphics.cpp

 	love::image::ImageData *data = nullptr;
 	love::image::CompressedData *cdata = nullptr;
 
-	Texture::Format format = Texture::FORMAT_NORMAL;
+	Image::Format format = Image::FORMAT_NORMAL;
 	const char *fstr = lua_isnoneornil(L, 2) ? nullptr : luaL_checkstring(L, 2);
 
-	if (fstr != nullptr && !Texture::getConstant(fstr, format))
-		return luaL_error(L, "Invalid texture format: %s", fstr);
-
-	if (format == Texture::FORMAT_HDR) // For now...
-		return luaL_error(L, "HDR images are not supported.");
+	if (fstr != nullptr && !Image::getConstant(fstr, format))
+		return luaL_error(L, "Invalid Image format: %s", fstr);
 
 	// Convert to FileData, if necessary.
 	if (lua_isstring(L, 1) || luax_istype(L, 1, FILESYSTEM_FILE_T))
 	const char *str = luaL_optstring(L, 3, "normal");
 	int fsaa        = luaL_optint(L, 4, 0);
 
-	Texture::Format format;
-	if (!Texture::getConstant(str, format))
-		return luaL_error(L, "Invalid texture format: %s", str);
+	Canvas::Format format;
+	if (!Canvas::getConstant(str, format))
+		return luaL_error(L, "Invalid Canvas format: %s", str);
 
 	Canvas *canvas = nullptr;
 	EXCEPT_GUARD(canvas = instance->newCanvas(width, height, format, fsaa);)
 				supported = false;
 			break;
 		case Graphics::SUPPORT_HDR_CANVAS:
-			if (!Canvas::isHDRSupported())
+			if (!Canvas::isFormatSupported(Canvas::FORMAT_HDR))
 				supported = false;
 			break;
 		case Graphics::SUPPORT_MULTI_CANVAS:
 				supported = false;
 			break;
 		case Graphics::SUPPORT_SRGB:
-			if (!Canvas::isSRGBSupported())
+			if (!Canvas::isFormatSupported(Canvas::FORMAT_SRGB))
 				supported = false;
 			break;
 		default:
 	return 1;
 }
 
+int w_hasCanvasFormat(lua_State *L)
+{
+	const char *str = luaL_checkstring(L, 1);
+	Canvas::Format format;
+
+	if (!Canvas::getConstant(str, format))
+		return luaL_error(L, "Invalid canvas format: %s", str);
+
+	luax_pushboolean(L, Canvas::isFormatSupported(format));
+	return 1;
+}
+
 int w_getRendererInfo(lua_State *L)
 {
 	std::string name, version, vendor, device;
 	{ "getShader", w_getShader },
 
 	{ "isSupported", w_isSupported },
+	{ "hasCanvasFormat", w_hasCanvasFormat },
 	{ "getRendererInfo", w_getRendererInfo },
 	{ "getSystemLimit", w_getSystemLimit },
 

File src/modules/graphics/opengl/wrap_Graphics.h

 int w_setShader(lua_State *L);
 int w_getShader(lua_State *L);
 int w_isSupported(lua_State *L);
+int w_hasCanvasFormat(lua_State *L);
 int w_getRendererInfo(lua_State *L);
 int w_getSystemLimit(lua_State *L);
 int w_draw(lua_State *L);