vrld avatar vrld committed 4296fcc

Fix Issue #476: Add HDR Canvas (thanks, Alexander Szpakowski!).

* Add `love.graphics.isSupported("hdrcanvas")' to query whether the hardware
actually supports HDR canvases.

* Add optional third argument to `love.graphics.newCanvas(w,h, type)', where
`type' is one of "normal" or "hdr" and defaults to "normal". HDR canvases
use floating point that can have values > 1 to store pixels, allowing for
HDR rendering and other higher level lighting techniques when combined with
pixel effects.

* Add `canvas:getType()'.

Comments (0)

Files changed (9)

src/modules/graphics/Graphics.cpp

 StringMap<Graphics::Support, Graphics::SUPPORT_MAX_ENUM>::Entry Graphics::supportEntries[] =
 {
 	{ "canvas", Graphics::SUPPORT_CANVAS },
+	{ "hdrcanvas", Graphics::SUPPORT_HDR_CANVAS },
 	{ "pixeleffect", Graphics::SUPPORT_PIXELEFFECT },
 	{ "npot", Graphics::SUPPORT_NPOT },
 	{ "subtractive", Graphics::SUPPORT_SUBTRACTIVE },

src/modules/graphics/Graphics.h

 	enum Support
 	{
 		SUPPORT_CANVAS = 1,
+		SUPPORT_HDR_CANVAS,
 		SUPPORT_PIXELEFFECT,
 		SUPPORT_NPOT,
 		SUPPORT_SUBTRACTIVE,

src/modules/graphics/opengl/Canvas.cpp

 	 * @param[out] img           Texture name
 	 * @param[in]  width         Width of framebuffer
 	 * @param[in]  height        Height of framebuffer
+	 * @param[in]  texture_type  Type of the canvas texture.
 	 * @return Creation status
 	 */
-	virtual GLenum createFBO(GLuint &, GLuint &, GLuint &, int, int)
+	virtual GLenum createFBO(GLuint &, GLuint &, GLuint &, int, int, Canvas::TextureType)
 	{
 		return GL_FRAMEBUFFER_UNSUPPORTED;
 	}
 
 struct FramebufferStrategyGL3 : public FramebufferStrategy
 {
-	virtual GLenum createFBO(GLuint &framebuffer, GLuint &depth_stencil,  GLuint &img, int width, int height)
+	virtual GLenum createFBO(GLuint &framebuffer, GLuint &depth_stencil,  GLuint &img, int width, int height, Canvas::TextureType texture_type)
 	{
 		// get currently bound fbo to reset to it later
 		GLint current_fbo;
 			GL_RENDERBUFFER, depth_stencil);
 
 		// generate texture save target
+		GLint internalFormat;
+		GLenum format;
+		switch (texture_type)
+		{
+			case Canvas::TYPE_HDR:
+				internalFormat = GL_RGBA16F;
+				format = GL_FLOAT;
+				break;
+			case Canvas::TYPE_NORMAL:
+			default:
+				internalFormat = GL_RGBA8;
+				format = GL_UNSIGNED_BYTE;
+		}
+
 		glGenTextures(1, &img);
 		bindTexture(img);
 		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
 		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
-		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA,
-			GL_UNSIGNED_BYTE, NULL);
+		glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, width, height,
+			0, GL_RGBA, format, NULL);
 		bindTexture(0);
 		glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
 			GL_TEXTURE_2D, img, 0);
 
 struct FramebufferStrategyPackedEXT : public FramebufferStrategy
 {
-	virtual GLenum createFBO(GLuint &framebuffer, GLuint &depth_stencil, GLuint &img, int width, int height)
+	virtual GLenum createFBO(GLuint &framebuffer, GLuint &depth_stencil, GLuint &img, int width, int height, Canvas::TextureType texture_type)
 	{
 		GLint current_fbo;
 		glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING_EXT, &current_fbo);
 			GL_RENDERBUFFER_EXT, depth_stencil);
 
 		// generate texture save target
+		GLint internalFormat;
+		GLenum format;
+		switch (texture_type)
+		{
+			case Canvas::TYPE_HDR:
+				internalFormat = GL_RGBA16F;
+				format = GL_FLOAT;
+				break;
+			case Canvas::TYPE_NORMAL:
+			default:
+				internalFormat = GL_RGBA8;
+				format = GL_UNSIGNED_BYTE;
+		}
+
 		glGenTextures(1, &img);
 		bindTexture(img);
 		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
 		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
-		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA,
-			GL_UNSIGNED_BYTE, NULL);
+
+		glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, width, height,
+			0, GL_RGBA, format, NULL);
 		bindTexture(0);
 		glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
 			GL_TEXTURE_2D, img, 0);
 
 struct FramebufferStrategyEXT : public FramebufferStrategyPackedEXT
 {
-	virtual GLenum createFBO(GLuint &framebuffer, GLuint &stencil, GLuint &img, int width, int height)
+	virtual GLenum createFBO(GLuint &framebuffer, GLuint &stencil, GLuint &img, int width, int height, Canvas::TextureType texture_type)
 	{
 		GLint current_fbo;
 		glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING_EXT, &current_fbo);
 			GL_RENDERBUFFER_EXT, stencil);
 
 		// generate texture save target
+		GLint internalFormat;
+		GLenum format;
+		switch (texture_type)
+		{
+			case Canvas::TYPE_HDR:
+				internalFormat = GL_RGBA16F;
+				format = GL_FLOAT;
+				break;
+			case Canvas::TYPE_NORMAL:
+			default:
+				internalFormat = GL_RGBA8;
+				format = GL_UNSIGNED_BYTE;
+		}
+
 		glGenTextures(1, &img);
 		bindTexture(img);
 		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
 		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
-		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA,
-			GL_UNSIGNED_BYTE, NULL);
+		glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, width, height,
+			0, GL_RGBA, format, NULL);
 		bindTexture(0);
 		glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
 			GL_TEXTURE_2D, img, 0);
 	bool isSupported()
 	{
 		GLuint fb, stencil, img;
-		GLenum status = createFBO(fb, stencil, img, 2, 2);
+		GLenum status = createFBO(fb, stencil, img, 2, 2, Canvas::TYPE_NORMAL);
 		deleteFBO(fb, stencil, img);
 		return status == GL_FRAMEBUFFER_COMPLETE;
 	}
 
 Canvas *Canvas::current = NULL;
 
-static void loadStrategy()
+static void getStrategy()
 {
 	if (!strategy)
 	{
 	}
 }
 
-Canvas::Canvas(int width, int height)
+Canvas::Canvas(int width, int height, TextureType texture_type)
 	: width(width)
 	, height(height)
+	, texture_type(texture_type)
 {
 	float w = static_cast<float>(width);
 	float h = static_cast<float>(height);
 	vertices[3].s = 1;
 	vertices[3].t = 1;
 
-	loadStrategy();
+	getStrategy();
 
 	loadVolatile();
 }
 
 bool Canvas::isSupported()
 {
-	loadStrategy();
+	getStrategy();
 	return (strategy != &strategyNone);
 }
 
+bool Canvas::isHdrSupported()
+{
+	return GLEE_VERSION_3_0 || GLEE_ARB_texture_float;
+}
+
 void Canvas::bindDefaultCanvas()
 {
 	if (current != NULL)
 
 bool Canvas::loadVolatile()
 {
-	status = strategy->createFBO(fbo, depth_stencil, img, width, height);
+	status = strategy->createFBO(fbo, depth_stencil, img, width, height, texture_type);
 	if (status != GL_FRAMEBUFFER_COMPLETE)
 		return false;
 
 	glPopMatrix();
 }
 
+bool Canvas::getConstant(const char *in, Canvas::TextureType &out)
+{
+	return textureTypes.find(in, out);
+}
+
+bool Canvas::getConstant(Canvas::TextureType in, const char *&out)
+{
+	return textureTypes.find(in, out);
+}
+
+StringMap<Canvas::TextureType, Canvas::TYPE_MAX_ENUM>::Entry Canvas::textureTypeEntries[] =
+{
+	{"normal", Canvas::TYPE_NORMAL},
+	{"hdr",    Canvas::TYPE_HDR},
+};
+StringMap<Canvas::TextureType, Canvas::TYPE_MAX_ENUM> Canvas::textureTypes(Canvas::textureTypeEntries, sizeof(Canvas::textureTypeEntries));
+
 } // opengl
 } // graphics
 } // love

src/modules/graphics/opengl/Canvas.h

 class Canvas : public DrawQable, public Volatile
 {
 public:
-	Canvas(int width, int height);
+	enum TextureType {
+		TYPE_NORMAL,
+		TYPE_HDR,
+		TYPE_MAX_ENUM
+	};
+
+	Canvas(int width, int height, TextureType texture_type = TYPE_NORMAL);
 	virtual ~Canvas();
 
-	static bool isSupported();
-
-	unsigned int getStatus() const
-	{
-		return status;
-	}
-
-	static Canvas *current;
-	static void bindDefaultCanvas();
-
 	void startGrab();
 	void stopGrab();
 
 	int getWidth();
 	int getHeight();
 
+	unsigned int getStatus() const
+	{
+		return status;
+	}
+
+	TextureType getTextureType() const
+	{
+		return texture_type;
+	}
+
 	bool loadVolatile();
 	void unloadVolatile();
 
+	static bool isSupported();
+	static bool isHdrSupported();
+	static bool getConstant(const char *in, TextureType &out);
+	static bool getConstant(TextureType in, const char *&out);
+
+	static Canvas *current;
+	static void bindDefaultCanvas();
+
 private:
 	friend class PixelEffect;
 	GLuint getTextureName() const
 	GLuint depth_stencil;
 	GLuint img;
 
+	TextureType texture_type;
+
 	vertex vertices[4];
 
 	GLenum status;
 	struct
 	{
 		Image::Filter filter;
-		Image::Wrap wrap;
+		Image::Wrap   wrap;
 	} settings;
 
 	void drawv(const Matrix &t, const vertex *v) const;
+
+	static StringMap<TextureType, TYPE_MAX_ENUM>::Entry textureTypeEntries[];
+	static StringMap<TextureType, TYPE_MAX_ENUM> textureTypes;
 };
 
 } // opengl

src/modules/graphics/opengl/Graphics.cpp

 	return new ParticleSystem(image, size);
 }
 
-Canvas *Graphics::newCanvas(int width, int height)
+Canvas *Graphics::newCanvas(int width, int height, Canvas::TextureType texture_type)
 {
-	Canvas *canvas = new Canvas(width, height);
+	if (texture_type == Canvas::TYPE_HDR && !Canvas::isHdrSupported())
+		throw Exception("HDR Canvases are not supported by your OpenGL implementation");
+
+	while (GL_NO_ERROR != glGetError())
+		/* clear opengl error flag */;
+
+	Canvas *canvas = new Canvas(width, height, texture_type);
 	GLenum err = canvas->getStatus();
 
 	// everything ok, return canvas (early out)

src/modules/graphics/opengl/Graphics.h

 
 	ParticleSystem *newParticleSystem(Image *image, int size);
 
-	Canvas *newCanvas(int width, int height);
+	Canvas *newCanvas(int width, int height, Canvas::TextureType texture_type = Canvas::TYPE_NORMAL);
 
 	PixelEffect *newPixelEffect(const std::string &code);
 

src/modules/graphics/opengl/wrap_Canvas.cpp

 	return 1;
 }
 
+int w_Canvas_getType(lua_State *L)
+{
+	Canvas *canvas = luax_checkcanvas(L, 1);
+	Canvas::TextureType type = canvas->getTextureType();
+	const char *str;
+	Canvas::getConstant(type, str);
+	lua_pushstring(L, str);
+	return 1;
+}
+
 static const luaL_Reg functions[] =
 {
 	{ "renderTo", w_Canvas_renderTo },
 	{ "clear", w_Canvas_clear },
 	{ "getWidth", w_Canvas_getWidth },
 	{ "getHeight", w_Canvas_getHeight },
+	{ "getType", w_Canvas_getType },
 	{ 0, 0 }
 };
 

src/modules/graphics/opengl/wrap_Canvas.h

 int w_Canvas_clear(lua_State *L);
 int w_Canvas_getWidth(lua_State *L);
 int w_Canvas_getHeight(lua_State *L);
+int w_Canvas_getType(lua_State *L);
 extern "C" int luaopen_canvas(lua_State *L);
 
 } // opengl

src/modules/graphics/opengl/wrap_Graphics.cpp

 int w_newCanvas(lua_State *L)
 {
 	// check if width and height are given. else default to screen dimensions.
-	int width  = luaL_optint(L, 1, instance->getWidth());
-	int height = luaL_optint(L, 2, instance->getHeight());
-	glGetError(); // clear opengl error flag
+	int width       = luaL_optint(L, 1, instance->getWidth());
+	int height      = luaL_optint(L, 2, instance->getHeight());
+	const char *str = luaL_optstring(L, 3, "normal");
+
+	Canvas::TextureType texture_type;
+	Canvas::getConstant(str, texture_type);
  1. Boolsheet

    The code should check if getConstant succeeds and throw an error on failure to minimize undefined behaviour.

    Edit: Unknown types will actually default to RGB8, but for consistency's sake I'd say it should error anyway.

 
 	Canvas *canvas = NULL;
 	try
 	{
-		canvas = instance->newCanvas(width, height);
+		canvas = instance->newCanvas(width, height, texture_type);
 	}
 	catch(Exception &e)
 	{
 			if (!Canvas::isSupported())
 				supported = false;
 			break;
+		case Graphics::SUPPORT_HDR_CANVAS:
+			if (!Canvas::isHdrSupported())
+				supported = false;
+			break;
 		case Graphics::SUPPORT_PIXELEFFECT:
 			if (!PixelEffect::isSupported())
 				supported = false;
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.