mirror of
https://github.com/love2d/love.git
synced 2026-08-12 08:30:56 +02:00
1187 lines
29 KiB
C++
1187 lines
29 KiB
C++
/**
|
|
* Copyright (c) 2006-2015 LOVE Development Team
|
|
*
|
|
* This software is provided 'as-is', without any express or implied
|
|
* warranty. In no event will the authors be held liable for any damages
|
|
* arising from the use of this software.
|
|
*
|
|
* Permission is granted to anyone to use this software for any purpose,
|
|
* including commercial applications, and to alter it and redistribute it
|
|
* freely, subject to the following restrictions:
|
|
*
|
|
* 1. The origin of this software must not be misrepresented; you must not
|
|
* claim that you wrote the original software. If you use this software
|
|
* in a product, an acknowledgment in the product documentation would be
|
|
* appreciated but is not required.
|
|
* 2. Altered source versions must be plainly marked as such, and must not be
|
|
* misrepresented as being the original software.
|
|
* 3. This notice may not be removed or altered from any source distribution.
|
|
**/
|
|
|
|
#include "Canvas.h"
|
|
#include "Image.h"
|
|
#include "Graphics.h"
|
|
#include "common/Matrix.h"
|
|
#include "common/config.h"
|
|
|
|
#include <cstring> // For memcpy
|
|
#include <limits>
|
|
|
|
namespace love
|
|
{
|
|
namespace graphics
|
|
{
|
|
namespace opengl
|
|
{
|
|
|
|
// strategy for fbo creation, interchangable at runtime:
|
|
// none, opengl >= 3.0, extensions
|
|
struct FramebufferStrategy
|
|
{
|
|
virtual ~FramebufferStrategy() {}
|
|
|
|
/// create a new framebuffer and texture
|
|
/**
|
|
* @param[out] framebuffer Framebuffer name
|
|
* @param[in] texture Texture name
|
|
* @return Creation status
|
|
*/
|
|
virtual GLenum createFBO(GLuint &, GLuint)
|
|
{
|
|
return GL_FRAMEBUFFER_UNSUPPORTED;
|
|
}
|
|
|
|
/// Create a stencil buffer and attach it to the active framebuffer object
|
|
/**
|
|
* @param[in] width Width of the stencil buffer
|
|
* @param[in] height Height of the stencil buffer
|
|
* @param[in] samples Number of samples to use
|
|
* @param[out] stencil Name for stencil buffer
|
|
* @return Whether the stencil buffer was successfully created
|
|
**/
|
|
virtual bool createStencil(int, int, int, GLuint &)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
/// Create a MSAA renderbuffer and attach it to the active FBO.
|
|
/**
|
|
* @param[in] width Width of the MSAA buffer
|
|
* @param[in] height Height of the MSAA buffer
|
|
* @param[inout] samples Number of samples to use
|
|
* @param[in] internalformat The internal format to use for the buffer
|
|
* @param[out] buffer Name for the MSAA buffer
|
|
* @return Whether the MSAA buffer was successfully created and attached
|
|
**/
|
|
virtual bool createMSAABuffer(int, int, int &, GLenum, GLuint &)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
/// remove objects
|
|
/**
|
|
* @param[in] framebuffer Framebuffer name
|
|
* @param[in] depth_stencil Name for packed depth and stencil buffer
|
|
*/
|
|
virtual void deleteFBO(GLuint, GLuint, GLuint) {}
|
|
virtual void bindFBO(GLuint) {}
|
|
|
|
/// attach additional canvases to the active framebuffer for rendering
|
|
/**
|
|
* @param[in] canvases List of canvases to attach
|
|
**/
|
|
virtual void setAttachments(const std::vector<Canvas *> &) {}
|
|
|
|
/// stop using all additional attached canvases
|
|
virtual void setAttachments() {}
|
|
};
|
|
|
|
struct FramebufferStrategyGL3 : public FramebufferStrategy
|
|
{
|
|
virtual GLenum createFBO(GLuint &framebuffer, GLuint texture)
|
|
{
|
|
// get currently bound fbo to reset to it later
|
|
GLint current_fbo;
|
|
glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, ¤t_fbo);
|
|
|
|
// create framebuffer
|
|
glGenFramebuffers(1, &framebuffer);
|
|
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
|
|
|
|
if (texture != 0)
|
|
{
|
|
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
|
|
GL_TEXTURE_2D, texture, 0);
|
|
}
|
|
|
|
// check status
|
|
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
|
|
|
|
// unbind framebuffer
|
|
glBindFramebuffer(GL_FRAMEBUFFER, (GLuint) current_fbo);
|
|
return status;
|
|
}
|
|
|
|
virtual bool createStencil(int width, int height, int samples, GLuint &stencil)
|
|
{
|
|
// create combined depth/stencil buffer
|
|
glDeleteRenderbuffers(1, &stencil);
|
|
glGenRenderbuffers(1, &stencil);
|
|
glBindRenderbuffer(GL_RENDERBUFFER, stencil);
|
|
|
|
if (samples > 1)
|
|
glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, GL_DEPTH_STENCIL, width, height);
|
|
else
|
|
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_STENCIL, width, height);
|
|
|
|
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT,
|
|
GL_RENDERBUFFER, stencil);
|
|
|
|
glBindRenderbuffer(GL_RENDERBUFFER, 0);
|
|
|
|
// check status
|
|
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
|
|
{
|
|
glDeleteRenderbuffers(1, &stencil);
|
|
stencil = 0;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
virtual bool createMSAABuffer(int width, int height, int &samples, GLenum internalformat, GLuint &buffer)
|
|
{
|
|
glGenRenderbuffers(1, &buffer);
|
|
glBindRenderbuffer(GL_RENDERBUFFER, buffer);
|
|
glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, internalformat,
|
|
width, height);
|
|
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
|
|
GL_RENDERBUFFER, buffer);
|
|
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_SAMPLES, &samples);
|
|
|
|
glBindRenderbuffer(GL_RENDERBUFFER, 0);
|
|
|
|
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
|
|
{
|
|
glDeleteRenderbuffers(1, &buffer);
|
|
buffer = 0;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
virtual void deleteFBO(GLuint framebuffer, GLuint depth_stencil, GLuint msaa_buffer)
|
|
{
|
|
if (depth_stencil != 0)
|
|
glDeleteRenderbuffers(1, &depth_stencil);
|
|
if (msaa_buffer != 0)
|
|
glDeleteRenderbuffers(1, &msaa_buffer);
|
|
if (framebuffer != 0)
|
|
glDeleteFramebuffers(1, &framebuffer);
|
|
}
|
|
|
|
virtual void bindFBO(GLuint framebuffer)
|
|
{
|
|
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
|
|
++Canvas::switchCount;
|
|
}
|
|
|
|
virtual void setAttachments()
|
|
{
|
|
// set a single render target
|
|
glDrawBuffer(GL_COLOR_ATTACHMENT0);
|
|
}
|
|
|
|
virtual void setAttachments(const std::vector<Canvas *> &canvases)
|
|
{
|
|
if (canvases.size() == 0)
|
|
{
|
|
setAttachments();
|
|
return;
|
|
}
|
|
|
|
std::vector<GLenum> drawbuffers;
|
|
drawbuffers.push_back(GL_COLOR_ATTACHMENT0);
|
|
|
|
// Attach the canvas textures to the currently bound framebuffer.
|
|
for (size_t i = 0; i < canvases.size(); i++)
|
|
{
|
|
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1 + i,
|
|
GL_TEXTURE_2D, canvases[i]->getGLTexture(), 0);
|
|
drawbuffers.push_back(GL_COLOR_ATTACHMENT1 + i);
|
|
}
|
|
|
|
// set up multiple render targets
|
|
if (GLEE_VERSION_2_0)
|
|
glDrawBuffers(drawbuffers.size(), &drawbuffers[0]);
|
|
else if (GLEE_ARB_draw_buffers)
|
|
glDrawBuffersARB(drawbuffers.size(), &drawbuffers[0]);
|
|
}
|
|
};
|
|
|
|
struct FramebufferStrategyPackedEXT : public FramebufferStrategy
|
|
{
|
|
virtual GLenum createFBO(GLuint &framebuffer, GLuint texture)
|
|
{
|
|
GLint current_fbo;
|
|
glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING_EXT, ¤t_fbo);
|
|
|
|
// create framebuffer
|
|
glGenFramebuffersEXT(1, &framebuffer);
|
|
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, framebuffer);
|
|
|
|
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
|
|
GL_TEXTURE_2D, texture, 0);
|
|
|
|
// check status
|
|
GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
|
|
|
|
// unbind framebuffer
|
|
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, (GLuint) current_fbo);
|
|
return status;
|
|
}
|
|
|
|
virtual bool createStencil(int width, int height, int samples, GLuint &stencil)
|
|
{
|
|
// create combined depth/stencil buffer
|
|
glDeleteRenderbuffersEXT(1, &stencil);
|
|
glGenRenderbuffersEXT(1, &stencil);
|
|
glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, stencil);
|
|
|
|
if (samples > 1)
|
|
{
|
|
glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER, samples,
|
|
GL_DEPTH_STENCIL, width, height);
|
|
}
|
|
else
|
|
{
|
|
glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_STENCIL_EXT,
|
|
width, height);
|
|
}
|
|
|
|
glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_STENCIL_ATTACHMENT_EXT,
|
|
GL_RENDERBUFFER_EXT, stencil);
|
|
|
|
glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0);
|
|
|
|
// check status
|
|
if (glCheckFramebufferStatusEXT(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
|
|
{
|
|
glDeleteRenderbuffersEXT(1, &stencil);
|
|
stencil = 0;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
virtual bool createMSAABuffer(int width, int height, int &samples, GLenum internalformat, GLuint &buffer)
|
|
{
|
|
if (!GLEE_EXT_framebuffer_multisample)
|
|
return false;
|
|
|
|
glGenRenderbuffersEXT(1, &buffer);
|
|
glBindRenderbufferEXT(GL_RENDERBUFFER, buffer);
|
|
glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER, samples,
|
|
internalformat, width, height);
|
|
glFramebufferRenderbufferEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
|
|
GL_RENDERBUFFER, buffer);
|
|
glGetRenderbufferParameterivEXT(GL_RENDERBUFFER, GL_RENDERBUFFER_SAMPLES, &samples);
|
|
|
|
glBindRenderbufferEXT(GL_RENDERBUFFER, 0);
|
|
|
|
if (glCheckFramebufferStatusEXT(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
|
|
{
|
|
glDeleteRenderbuffersEXT(1, &buffer);
|
|
buffer = 0;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
virtual void deleteFBO(GLuint framebuffer, GLuint depth_stencil, GLuint msaa_buffer)
|
|
{
|
|
if (depth_stencil != 0)
|
|
glDeleteRenderbuffersEXT(1, &depth_stencil);
|
|
if (msaa_buffer != 0)
|
|
glDeleteRenderbuffersEXT(1, &msaa_buffer);
|
|
if (framebuffer != 0)
|
|
glDeleteFramebuffersEXT(1, &framebuffer);
|
|
}
|
|
|
|
virtual void bindFBO(GLuint framebuffer)
|
|
{
|
|
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, framebuffer);
|
|
++Canvas::switchCount;
|
|
}
|
|
|
|
virtual void setAttachments()
|
|
{
|
|
// set a single render target
|
|
glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT);
|
|
}
|
|
|
|
virtual void setAttachments(const std::vector<Canvas *> &canvases)
|
|
{
|
|
if (canvases.size() == 0)
|
|
{
|
|
setAttachments();
|
|
return;
|
|
}
|
|
|
|
std::vector<GLenum> drawbuffers;
|
|
drawbuffers.push_back(GL_COLOR_ATTACHMENT0_EXT);
|
|
|
|
// Attach the canvas textures to the currently bound framebuffer.
|
|
for (size_t i = 0; i < canvases.size(); i++)
|
|
{
|
|
glFramebufferTexture2DEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1_EXT + i,
|
|
GL_TEXTURE_2D, canvases[i]->getGLTexture(), 0);
|
|
drawbuffers.push_back(GL_COLOR_ATTACHMENT1_EXT + i);
|
|
}
|
|
|
|
// set up multiple render targets
|
|
if (GLEE_VERSION_2_0)
|
|
glDrawBuffers(drawbuffers.size(), &drawbuffers[0]);
|
|
else if (GLEE_ARB_draw_buffers)
|
|
glDrawBuffersARB(drawbuffers.size(), &drawbuffers[0]);
|
|
}
|
|
};
|
|
|
|
struct FramebufferStrategyEXT : public FramebufferStrategyPackedEXT
|
|
{
|
|
virtual bool createStencil(int width, int height, int samples, GLuint &stencil)
|
|
{
|
|
// create stencil buffer
|
|
glDeleteRenderbuffersEXT(1, &stencil);
|
|
glGenRenderbuffersEXT(1, &stencil);
|
|
glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, stencil);
|
|
|
|
if (samples > 1)
|
|
{
|
|
glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER, samples,
|
|
GL_STENCIL_INDEX, width, height);
|
|
}
|
|
else
|
|
{
|
|
glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_STENCIL_INDEX,
|
|
width, height);
|
|
}
|
|
|
|
glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_STENCIL_ATTACHMENT_EXT,
|
|
GL_RENDERBUFFER_EXT, stencil);
|
|
|
|
glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0);
|
|
|
|
// check status
|
|
if (glCheckFramebufferStatusEXT(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
|
|
{
|
|
glDeleteRenderbuffersEXT(1, &stencil);
|
|
stencil = 0;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool isSupported()
|
|
{
|
|
GLuint fb = 0, stencil = 0;
|
|
GLenum status = createFBO(fb, 0);
|
|
deleteFBO(fb, stencil, 0);
|
|
return status == GL_FRAMEBUFFER_COMPLETE;
|
|
}
|
|
};
|
|
|
|
FramebufferStrategy *strategy = nullptr;
|
|
|
|
FramebufferStrategy strategyNone;
|
|
|
|
FramebufferStrategyGL3 strategyGL3;
|
|
|
|
FramebufferStrategyPackedEXT strategyPackedEXT;
|
|
|
|
FramebufferStrategyEXT strategyEXT;
|
|
|
|
Canvas *Canvas::current = nullptr;
|
|
OpenGL::Viewport Canvas::systemViewport = OpenGL::Viewport();
|
|
bool Canvas::screenHasSRGB = false;
|
|
int Canvas::switchCount = 0;
|
|
int Canvas::canvasCount = 0;
|
|
|
|
static void getStrategy()
|
|
{
|
|
if (!strategy)
|
|
{
|
|
if (GLEE_VERSION_3_0 || GLEE_ARB_framebuffer_object)
|
|
strategy = &strategyGL3;
|
|
else if (GLEE_EXT_framebuffer_object && GLEE_EXT_packed_depth_stencil)
|
|
strategy = &strategyPackedEXT;
|
|
else if (GLEE_EXT_framebuffer_object && strategyEXT.isSupported())
|
|
strategy = &strategyEXT;
|
|
else
|
|
strategy = &strategyNone;
|
|
}
|
|
}
|
|
|
|
Canvas::Canvas(int width, int height, Format format, int msaa)
|
|
: fbo(0)
|
|
, resolve_fbo(0)
|
|
, texture(0)
|
|
, msaa_buffer(0)
|
|
, depth_stencil(0)
|
|
, format(format)
|
|
, msaa_samples(msaa)
|
|
, msaa_dirty(false)
|
|
, texture_memory(0)
|
|
{
|
|
this->width = width;
|
|
this->height = height;
|
|
|
|
float w = static_cast<float>(width);
|
|
float h = static_cast<float>(height);
|
|
|
|
// world coordinates
|
|
vertices[0].x = 0;
|
|
vertices[0].y = 0;
|
|
vertices[1].x = 0;
|
|
vertices[1].y = h;
|
|
vertices[2].x = w;
|
|
vertices[2].y = h;
|
|
vertices[3].x = w;
|
|
vertices[3].y = 0;
|
|
|
|
// texture coordinates
|
|
vertices[0].s = 0;
|
|
vertices[0].t = 0;
|
|
vertices[1].s = 0;
|
|
vertices[1].t = 1;
|
|
vertices[2].s = 1;
|
|
vertices[2].t = 1;
|
|
vertices[3].s = 1;
|
|
vertices[3].t = 0;
|
|
|
|
getStrategy();
|
|
|
|
loadVolatile();
|
|
|
|
++canvasCount;
|
|
}
|
|
|
|
Canvas::~Canvas()
|
|
{
|
|
--canvasCount;
|
|
|
|
// reset framebuffer if still using this one
|
|
if (current == this)
|
|
stopGrab();
|
|
|
|
unloadVolatile();
|
|
}
|
|
|
|
bool Canvas::createMSAAFBO(GLenum internalformat)
|
|
{
|
|
// Create our FBO without a texture.
|
|
status = strategy->createFBO(fbo, 0);
|
|
|
|
GLuint previous = 0;
|
|
if (current != this)
|
|
{
|
|
if (current != nullptr)
|
|
previous = current->fbo;
|
|
|
|
strategy->bindFBO(fbo);
|
|
}
|
|
|
|
// Create and attach the MSAA buffer for our FBO.
|
|
if (strategy->createMSAABuffer(width, height, msaa_samples, internalformat, msaa_buffer))
|
|
status = GL_FRAMEBUFFER_COMPLETE;
|
|
else
|
|
status = GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT;
|
|
|
|
// Create the FBO used for the MSAA resolve, and attach the texture.
|
|
if (status == GL_FRAMEBUFFER_COMPLETE)
|
|
status = strategy->createFBO(resolve_fbo, texture);
|
|
|
|
if (status != GL_FRAMEBUFFER_COMPLETE)
|
|
{
|
|
// Clean up.
|
|
strategy->deleteFBO(fbo, 0, msaa_buffer);
|
|
strategy->deleteFBO(resolve_fbo, 0, 0);
|
|
fbo = msaa_buffer = resolve_fbo = 0;
|
|
msaa_samples = 0;
|
|
}
|
|
|
|
if (current != this)
|
|
strategy->bindFBO(previous);
|
|
|
|
return status == GL_FRAMEBUFFER_COMPLETE;
|
|
}
|
|
|
|
bool Canvas::loadVolatile()
|
|
{
|
|
fbo = depth_stencil = texture = 0;
|
|
resolve_fbo = msaa_buffer = 0;
|
|
status = GL_FRAMEBUFFER_COMPLETE;
|
|
|
|
// glTexImage2D is guaranteed to error in this case.
|
|
if (width > gl.getMaxTextureSize() || height > gl.getMaxTextureSize())
|
|
{
|
|
status = GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT;
|
|
return false;
|
|
}
|
|
|
|
glGenTextures(1, &texture);
|
|
gl.bindTexture(texture);
|
|
|
|
setFilter(filter);
|
|
setWrap(wrap);
|
|
|
|
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,
|
|
(GLint) internalformat,
|
|
width, height,
|
|
0,
|
|
externalformat,
|
|
textype,
|
|
nullptr);
|
|
|
|
if (glGetError() != GL_NO_ERROR)
|
|
{
|
|
gl.deleteTexture(texture);
|
|
texture = 0;
|
|
status = GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT;
|
|
return false;
|
|
}
|
|
|
|
int max_samples = 0;
|
|
if (GLEE_VERSION_3_0 || GLEE_ARB_framebuffer_object
|
|
|| GLEE_EXT_framebuffer_multisample)
|
|
{
|
|
glGetIntegerv(GL_MAX_SAMPLES, &max_samples);
|
|
}
|
|
|
|
if (msaa_samples > max_samples)
|
|
msaa_samples = max_samples;
|
|
|
|
// Try to create a MSAA FBO if requested.
|
|
bool msaasuccess = false;
|
|
if (msaa_samples > 1)
|
|
msaasuccess = createMSAAFBO(internalformat);
|
|
|
|
// On failure (or no requested MSAA), fall back to a regular FBO.
|
|
if (!msaasuccess)
|
|
status = strategy->createFBO(fbo, texture);
|
|
|
|
if (status != GL_FRAMEBUFFER_COMPLETE)
|
|
{
|
|
if (fbo != 0)
|
|
{
|
|
strategy->deleteFBO(fbo, 0, 0);
|
|
fbo = 0;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
clear(Color(0, 0, 0, 0));
|
|
|
|
msaa_dirty = (msaa_buffer != 0);
|
|
|
|
size_t prevmemsize = texture_memory;
|
|
|
|
texture_memory = (getFormatBitsPerPixel(format) * width * height) / 8;
|
|
if (msaa_buffer != 0)
|
|
texture_memory += (texture_memory * msaa_samples);
|
|
|
|
gl.updateTextureMemorySize(prevmemsize, texture_memory);
|
|
|
|
return true;
|
|
}
|
|
|
|
void Canvas::unloadVolatile()
|
|
{
|
|
strategy->deleteFBO(fbo, depth_stencil, msaa_buffer);
|
|
strategy->deleteFBO(resolve_fbo, 0, 0);
|
|
|
|
gl.deleteTexture(texture);
|
|
|
|
fbo = depth_stencil = texture = 0;
|
|
resolve_fbo = msaa_buffer = 0;
|
|
|
|
attachedCanvases.clear();
|
|
|
|
gl.updateTextureMemorySize(texture_memory, 0);
|
|
texture_memory = 0;
|
|
}
|
|
|
|
void Canvas::drawv(const Matrix &t, const Vertex *v)
|
|
{
|
|
OpenGL::TempTransform transform(gl);
|
|
transform.get() *= t;
|
|
|
|
predraw();
|
|
|
|
glEnableClientState(GL_VERTEX_ARRAY);
|
|
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
|
|
|
|
glVertexPointer(2, GL_FLOAT, sizeof(Vertex), (GLvoid *)&v[0].x);
|
|
glTexCoordPointer(2, GL_FLOAT, sizeof(Vertex), (GLvoid *)&v[0].s);
|
|
|
|
gl.prepareDraw();
|
|
gl.drawArrays(GL_QUADS, 0, 4);
|
|
|
|
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
|
|
glDisableClientState(GL_VERTEX_ARRAY);
|
|
|
|
postdraw();
|
|
}
|
|
|
|
void Canvas::draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky)
|
|
{
|
|
static Matrix t;
|
|
t.setTransformation(x, y, angle, sx, sy, ox, oy, kx, ky);
|
|
|
|
drawv(t, vertices);
|
|
}
|
|
|
|
void Canvas::drawq(Quad *quad, float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky)
|
|
{
|
|
static Matrix t;
|
|
t.setTransformation(x, y, angle, sx, sy, ox, oy, kx, ky);
|
|
|
|
const Vertex *v = quad->getVertices();
|
|
drawv(t, v);
|
|
}
|
|
|
|
void Canvas::setFilter(const Texture::Filter &f)
|
|
{
|
|
filter = f;
|
|
gl.bindTexture(texture);
|
|
gl.setTextureFilter(filter);
|
|
}
|
|
|
|
void Canvas::setWrap(const Texture::Wrap &w)
|
|
{
|
|
wrap = w;
|
|
gl.bindTexture(texture);
|
|
gl.setTextureWrap(wrap);
|
|
}
|
|
|
|
GLuint Canvas::getGLTexture() const
|
|
{
|
|
return texture;
|
|
}
|
|
|
|
void Canvas::predraw()
|
|
{
|
|
// We need to make sure the texture is up-to-date by resolving the MSAA
|
|
// buffer (which we render to when the canvas is active) to it.
|
|
resolveMSAA();
|
|
|
|
gl.bindTexture(texture);
|
|
}
|
|
|
|
void Canvas::setupGrab()
|
|
{
|
|
// already grabbing
|
|
if (current == this)
|
|
return;
|
|
|
|
// cleanup after previous Canvas
|
|
if (current != nullptr)
|
|
{
|
|
systemViewport = current->systemViewport;
|
|
current->stopGrab(true);
|
|
}
|
|
else
|
|
systemViewport = gl.getViewport();
|
|
|
|
// indicate we are using this Canvas.
|
|
current = this;
|
|
|
|
// bind the framebuffer object.
|
|
strategy->bindFBO(fbo);
|
|
gl.setViewport(OpenGL::Viewport(0, 0, width, height));
|
|
|
|
// Set up the projection matrix
|
|
gl.matrices.projection.push_back(Matrix::ortho(0.0, width, 0.0, height));
|
|
|
|
// Make sure the correct sRGB setting is used when drawing to the canvas.
|
|
if (format == FORMAT_SRGB)
|
|
glEnable(GL_FRAMEBUFFER_SRGB);
|
|
else if (screenHasSRGB)
|
|
glDisable(GL_FRAMEBUFFER_SRGB);
|
|
|
|
if (msaa_buffer != 0)
|
|
msaa_dirty = true;
|
|
}
|
|
|
|
void Canvas::startGrab(const std::vector<Canvas *> &canvases)
|
|
{
|
|
// Whether the new canvas list is different from the old one.
|
|
// A more thorough check is done below.
|
|
bool canvaseschanged = canvases.size() != attachedCanvases.size();
|
|
|
|
if (canvases.size() > 0)
|
|
{
|
|
if (!isMultiCanvasSupported())
|
|
throw love::Exception("Multi-canvas rendering is not supported on this system.");
|
|
|
|
if ((int) canvases.size() + 1 > gl.getMaxRenderTargets())
|
|
throw love::Exception("This system can't simultaniously render to %d canvases.", canvases.size()+1);
|
|
|
|
if (msaa_samples != 0)
|
|
throw love::Exception("Multi-canvas rendering is not supported with MSAA.");
|
|
}
|
|
|
|
for (size_t i = 0; i < canvases.size(); i++)
|
|
{
|
|
if (canvases[i]->getWidth() != width || canvases[i]->getHeight() != height)
|
|
throw love::Exception("All canvas arguments must have the same dimensions.");
|
|
|
|
if (canvases[i]->getTextureFormat() != format)
|
|
throw love::Exception("All canvas arguments must have the same texture format.");
|
|
|
|
if (canvases[i]->getMSAA() != 0)
|
|
throw love::Exception("Multi-canvas rendering is not supported with MSAA.");
|
|
|
|
if (!canvaseschanged && canvases[i] != attachedCanvases[i])
|
|
canvaseschanged = true;
|
|
}
|
|
|
|
setupGrab();
|
|
|
|
// Don't attach anything if there's nothing to change.
|
|
if (!canvaseschanged)
|
|
return;
|
|
|
|
// Attach the canvas textures to the active FBO and set up MRTs.
|
|
strategy->setAttachments(canvases);
|
|
|
|
// We want to avoid reference cycles, so we don't retain the attached
|
|
// Canvases here. The code in Graphics::setCanvas retains them.
|
|
|
|
attachedCanvases = canvases;
|
|
}
|
|
|
|
void Canvas::startGrab()
|
|
{
|
|
setupGrab();
|
|
|
|
if (attachedCanvases.size() == 0)
|
|
return;
|
|
|
|
// make sure the FBO is only using a single canvas
|
|
strategy->setAttachments();
|
|
|
|
attachedCanvases.clear();
|
|
}
|
|
|
|
void Canvas::stopGrab(bool switchingToOtherCanvas)
|
|
{
|
|
// i am not grabbing. leave me alone
|
|
if (current != this)
|
|
return;
|
|
|
|
gl.matrices.projection.pop_back();
|
|
|
|
if (switchingToOtherCanvas)
|
|
{
|
|
if (format == FORMAT_SRGB)
|
|
glDisable(GL_FRAMEBUFFER_SRGB);
|
|
}
|
|
else
|
|
{
|
|
// bind system framebuffer.
|
|
strategy->bindFBO(0);
|
|
current = nullptr;
|
|
gl.setViewport(systemViewport);
|
|
|
|
if (format == FORMAT_SRGB && !screenHasSRGB)
|
|
glDisable(GL_FRAMEBUFFER_SRGB);
|
|
else if (format != FORMAT_SRGB && screenHasSRGB)
|
|
glEnable(GL_FRAMEBUFFER_SRGB);
|
|
}
|
|
}
|
|
|
|
void Canvas::clear(Color c)
|
|
{
|
|
if (strategy == &strategyNone)
|
|
return;
|
|
|
|
GLuint previous = 0;
|
|
|
|
if (current != this)
|
|
{
|
|
if (current != nullptr)
|
|
previous = current->fbo;
|
|
|
|
strategy->bindFBO(fbo);
|
|
}
|
|
|
|
GLfloat glcolor[] = {c.r/255.f, c.g/255.f, c.b/255.f, c.a/255.f};
|
|
|
|
// We don't need to worry about multiple FBO attachments or global clear
|
|
// color state when OpenGL 3.0+ is supported.
|
|
if (GLEE_VERSION_3_0)
|
|
{
|
|
glClearBufferfv(GL_COLOR, 0, glcolor);
|
|
|
|
if (depth_stencil != 0)
|
|
{
|
|
GLint stencilvalue = 0;
|
|
glClearBufferiv(GL_STENCIL, 0, &stencilvalue);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// glClear will clear all active draw buffers, so we need to temporarily
|
|
// detach any other canvases (when MRT is being used.)
|
|
if (attachedCanvases.size() > 0)
|
|
strategy->setAttachments();
|
|
|
|
// Don't use the state-shadowed gl.setClearColor because we want to save
|
|
// the previous clear color.
|
|
glClearColor(glcolor[0], glcolor[1], glcolor[2], glcolor[3]);
|
|
glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
|
|
|
|
if (attachedCanvases.size() > 0)
|
|
strategy->setAttachments(attachedCanvases);
|
|
|
|
// Restore the global clear color.
|
|
gl.setClearColor(gl.getClearColor());
|
|
}
|
|
|
|
if (current != this)
|
|
strategy->bindFBO(previous);
|
|
|
|
if (msaa_buffer != 0)
|
|
msaa_dirty = true;
|
|
}
|
|
|
|
bool Canvas::checkCreateStencil()
|
|
{
|
|
// Do nothing if we've already created the stencil buffer.
|
|
if (depth_stencil != 0)
|
|
return true;
|
|
|
|
if (current != this)
|
|
strategy->bindFBO(fbo);
|
|
|
|
bool success = strategy->createStencil(width, height, msaa_samples, depth_stencil);
|
|
|
|
if (current && current != this)
|
|
strategy->bindFBO(current->fbo);
|
|
else if (!current)
|
|
strategy->bindFBO(0);
|
|
|
|
return success;
|
|
}
|
|
|
|
love::image::ImageData *Canvas::getImageData(love::image::Image *image)
|
|
{
|
|
resolveMSAA();
|
|
|
|
int row = 4 * width;
|
|
int size = row * height;
|
|
GLubyte *pixels = new GLubyte[size];
|
|
|
|
// Our texture is attached to 'resolve_fbo' when we use MSAA.
|
|
if (msaa_samples > 1 && (GLEE_VERSION_3_0 || GLEE_ARB_framebuffer_object))
|
|
glBindFramebuffer(GL_READ_FRAMEBUFFER, resolve_fbo);
|
|
else if (msaa_samples > 1 && GLEE_EXT_framebuffer_multisample)
|
|
glBindFramebufferEXT(GL_READ_FRAMEBUFFER, resolve_fbo);
|
|
else
|
|
strategy->bindFBO(fbo);
|
|
|
|
glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
|
|
|
|
if (current)
|
|
strategy->bindFBO(current->fbo);
|
|
else
|
|
strategy->bindFBO(0);
|
|
|
|
// The new ImageData now owns the pixel data, so we don't delete it here.
|
|
love::image::ImageData *img = image->newImageData(width, height, (void *)pixels, true);
|
|
return img;
|
|
}
|
|
|
|
void Canvas::getPixel(unsigned char* pixel_rgba, int x, int y)
|
|
{
|
|
if (x < 0 || x >= width || y < 0 || y >= width)
|
|
throw love::Exception("Attempt to get out-of-range pixel (%d,%d)!", x, y);
|
|
|
|
resolveMSAA();
|
|
|
|
// Our texture is attached to 'resolve_fbo' when we use MSAA.
|
|
if (msaa_samples > 1 && (GLEE_VERSION_3_0 || GLEE_ARB_framebuffer_object))
|
|
glBindFramebuffer(GL_READ_FRAMEBUFFER, resolve_fbo);
|
|
else if (msaa_samples > 1 && GLEE_EXT_framebuffer_multisample)
|
|
glBindFramebufferEXT(GL_READ_FRAMEBUFFER, resolve_fbo);
|
|
else if (current != this)
|
|
strategy->bindFBO(fbo);
|
|
|
|
glReadPixels(x, y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel_rgba);
|
|
|
|
if (current && current != this)
|
|
strategy->bindFBO(current->fbo);
|
|
else if (!current)
|
|
strategy->bindFBO(0);
|
|
}
|
|
|
|
bool Canvas::resolveMSAA()
|
|
{
|
|
if (resolve_fbo == 0 || msaa_buffer == 0)
|
|
return false;
|
|
|
|
if (!msaa_dirty)
|
|
return true;
|
|
|
|
GLuint previous = 0;
|
|
if (current != nullptr)
|
|
previous = current->fbo;
|
|
|
|
// Do the MSAA resolve by blitting the MSAA renderbuffer to the texture.
|
|
if (GLEE_VERSION_3_0 || GLEE_ARB_framebuffer_object)
|
|
{
|
|
glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo);
|
|
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, resolve_fbo);
|
|
glBlitFramebuffer(0, 0, width, height, 0, 0, width, height,
|
|
GL_COLOR_BUFFER_BIT, GL_NEAREST);
|
|
}
|
|
else if (GLEE_EXT_framebuffer_multisample && GLEE_EXT_framebuffer_blit)
|
|
{
|
|
glBindFramebufferEXT(GL_READ_FRAMEBUFFER, fbo);
|
|
glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER, resolve_fbo);
|
|
glBlitFramebufferEXT(0, 0, width, height, 0, 0, width, height,
|
|
GL_COLOR_BUFFER_BIT, GL_NEAREST);
|
|
}
|
|
else
|
|
return false;
|
|
|
|
strategy->bindFBO(previous);
|
|
|
|
if (current != this)
|
|
msaa_dirty = false;
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
size_t Canvas::getFormatBitsPerPixel(Format format)
|
|
{
|
|
switch (getSizedFormat(format))
|
|
{
|
|
case FORMAT_RGBA8:
|
|
case FORMAT_RGB10A2:
|
|
case FORMAT_RG11B10F:
|
|
case FORMAT_SRGB:
|
|
default:
|
|
return 32;
|
|
case FORMAT_RGBA4:
|
|
case FORMAT_RGB5A1:
|
|
case FORMAT_RGB565:
|
|
return 16;
|
|
case FORMAT_RGBA16F:
|
|
return 64;
|
|
case FORMAT_RGBA32F:
|
|
return 128;
|
|
}
|
|
}
|
|
|
|
bool Canvas::isSupported()
|
|
{
|
|
getStrategy();
|
|
return (strategy != &strategyNone);
|
|
}
|
|
|
|
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_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;
|
|
}
|
|
|
|
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
|