Add a new “stencil8” pixel format for Canvases (resolves issue #1003).

Also fixed Canvas MSAA (resolves issue #1271).

stencil-formatted Canvases can’t be drawn, and can only be used as the value for a new ‘depthstencil’ field to the table-argument variant of love.graphics.setCanvas.

--HG--
branch : minor
This commit is contained in:
Alex Szpakowski
2017-04-11 00:00:22 -03:00
parent 08a377ff30
commit 018a3831cb
23 changed files with 518 additions and 321 deletions
+121 -86
View File
@@ -69,8 +69,9 @@ static GLenum createFBO(GLuint &framebuffer, TextureType texType, GLuint texture
return status;
}
static bool createMSAABuffer(int width, int height, int &samples, PixelFormat pixelformat, GLuint &buffer)
static bool createRenderbuffer(int width, int height, int &samples, PixelFormat pixelformat, GLuint &buffer)
{
int reqsamples = samples;
bool unusedSRGB = false;
OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(pixelformat, true, unusedSRGB);
@@ -84,16 +85,23 @@ static bool createMSAABuffer(int width, int height, int &samples, PixelFormat pi
glGenRenderbuffers(1, &buffer);
glBindRenderbuffer(GL_RENDERBUFFER, buffer);
glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, fmt.internalformat, width, height);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, buffer);
if (samples > 1)
glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, fmt.internalformat, width, height);
else
glRenderbufferStorage(GL_RENDERBUFFER, fmt.internalformat, width, height);
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_SAMPLES, &samples);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, fmt.framebufferAttachments[0], GL_RENDERBUFFER, buffer);
if (samples > 1)
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_SAMPLES, &samples);
else
samples = 0;
glBindRenderbuffer(GL_RENDERBUFFER, 0);
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (status == GL_FRAMEBUFFER_COMPLETE && samples > 1)
if (status == GL_FRAMEBUFFER_COMPLETE && (reqsamples <= 1 || samples > 1))
{
// Initialize the buffer to transparent black.
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
@@ -109,16 +117,17 @@ static bool createMSAABuffer(int width, int height, int &samples, PixelFormat pi
gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, current_fbo);
gl.deleteFramebuffer(fbo);
return status == GL_FRAMEBUFFER_COMPLETE && samples > 1;
return status == GL_FRAMEBUFFER_COMPLETE;
}
Canvas::Canvas(const Settings &settings)
: love::graphics::Canvas(settings.type)
, fbo(0)
, texture(0)
, msaa_buffer(0)
, actual_samples(0)
, texture_memory(0)
, renderbuffer(0)
, requestedSamples(settings.msaa)
, actualSamples(0)
, textureMemory(0)
{
this->width = settings.width;
this->height = settings.height;
@@ -140,6 +149,8 @@ Canvas::Canvas(const Settings &settings)
this->format = getSizedFormat(settings.format);
readable = this->format != PIXELFORMAT_STENCIL8;
initQuad();
loadVolatile();
@@ -167,9 +178,12 @@ bool Canvas::loadVolatile()
throw love::Exception("The %s canvas format is not supported by your OpenGL drivers.", fstr);
}
if (settings.msaa > 1 && texType != TEXTURE_2D)
if (requestedSamples > 1 && texType != TEXTURE_2D)
throw love::Exception("MSAA is only supported for 2D texture types.");
if (!readable && texType != TEXTURE_2D)
throw love::Exception("Non-readable pixel formats are only supported for 2D texture types.");
if (!gl.isTextureTypeSupported(texType))
{
const char *textypestr = "unknown";
@@ -214,68 +228,74 @@ bool Canvas::loadVolatile()
OpenGL::TempDebugGroup debuggroup("Canvas load");
fbo = texture = 0;
msaa_buffer = 0;
renderbuffer = 0;
status = GL_FRAMEBUFFER_COMPLETE;
if (isReadable())
{
glGenTextures(1, &texture);
gl.bindTextureToUnit(this, 0, false);
GLenum gltype = OpenGL::getGLTextureType(texType);
if (GLAD_ANGLE_texture_usage)
glTexParameteri(gltype, GL_TEXTURE_USAGE_ANGLE, GL_FRAMEBUFFER_ATTACHMENT_ANGLE);
setFilter(filter);
setWrap(wrap);
while (glGetError() != GL_NO_ERROR)
/* Clear the error buffer. */;
bool isSRGB = format == PIXELFORMAT_sRGBA8;
if (!gl.rawTexStorage(texType, 1, format, isSRGB, pixelWidth, pixelHeight, texType == TEXTURE_VOLUME ? depth : layers))
{
status = GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT;
return false;
}
if (glGetError() != GL_NO_ERROR)
{
gl.deleteTexture(texture);
texture = 0;
status = GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT;
return false;
}
// Create a canvas-local FBO used for glReadPixels as well as MSAA blitting.
status = createFBO(fbo, texType, texture, texType == TEXTURE_VOLUME ? depth : layers, true);
if (status != GL_FRAMEBUFFER_COMPLETE)
{
if (fbo != 0)
{
gl.deleteFramebuffer(fbo);
fbo = 0;
}
return false;
}
}
// getMaxRenderbufferSamples will be 0 on systems that don't support
// multisampled renderbuffers / don't export FBO multisample extensions.
settings.msaa = std::min(settings.msaa, gl.getMaxRenderbufferSamples());
settings.msaa = std::max(settings.msaa, 0);
actualSamples = requestedSamples;
actualSamples = std::min(actualSamples, gl.getMaxRenderbufferSamples());
actualSamples = std::max(actualSamples, 0);
actualSamples = actualSamples == 1 ? 0 : actualSamples;
glGenTextures(1, &texture);
gl.bindTextureToUnit(this, 0, false);
if (!isReadable() || actualSamples > 0)
createRenderbuffer(pixelWidth, pixelHeight, actualSamples, format, renderbuffer);
GLenum gltype = OpenGL::getGLTextureType(texType);
size_t prevmemsize = textureMemory;
if (GLAD_ANGLE_texture_usage)
glTexParameteri(gltype, GL_TEXTURE_USAGE_ANGLE, GL_FRAMEBUFFER_ATTACHMENT_ANGLE);
textureMemory = getPixelFormatSize(format) * pixelWidth * pixelHeight;
setFilter(filter);
setWrap(wrap);
if (actualSamples > 0 && isReadable())
textureMemory += (textureMemory * actualSamples);
else if (actualSamples > 0)
textureMemory *= actualSamples;
while (glGetError() != GL_NO_ERROR)
/* Clear the error buffer. */;
bool isSRGB = format == PIXELFORMAT_sRGBA8;
if (!gl.rawTexStorage(texType, 1, format, isSRGB, pixelWidth, pixelHeight, texType == TEXTURE_VOLUME ? depth : layers))
{
status = GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT;
return false;
}
if (glGetError() != GL_NO_ERROR)
{
gl.deleteTexture(texture);
texture = 0;
status = GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT;
return false;
}
// Create a canvas-local FBO used for glReadPixels as well as MSAA blitting.
status = createFBO(fbo, texType, texture, texType == TEXTURE_VOLUME ? depth : layers, true);
if (status != GL_FRAMEBUFFER_COMPLETE)
{
if (fbo != 0)
{
gl.deleteFramebuffer(fbo);
fbo = 0;
}
return false;
}
actual_samples = settings.msaa == 1 ? 0 : settings.msaa;
if (actual_samples > 0 && !createMSAABuffer(pixelWidth, pixelHeight, actual_samples, format, msaa_buffer))
actual_samples = 0;
size_t prevmemsize = texture_memory;
texture_memory = getPixelFormatSize(format) * pixelWidth * pixelHeight;
if (msaa_buffer != 0)
texture_memory += (texture_memory * actual_samples);
gl.updateTextureMemorySize(prevmemsize, texture_memory);
gl.updateTextureMemorySize(prevmemsize, textureMemory);
return true;
}
@@ -285,18 +305,18 @@ void Canvas::unloadVolatile()
if (fbo != 0)
gl.deleteFramebuffer(fbo);
if (msaa_buffer != 0)
glDeleteRenderbuffers(1, &msaa_buffer);
if (renderbuffer != 0)
glDeleteRenderbuffers(1, &renderbuffer);
if (texture != 0)
gl.deleteTexture(texture);
fbo = 0;
msaa_buffer = 0;
renderbuffer = 0;
texture = 0;
gl.updateTextureMemorySize(texture_memory, 0);
texture_memory = 0;
gl.updateTextureMemorySize(textureMemory, 0);
textureMemory = 0;
}
void Canvas::setFilter(const Texture::Filter &f)
@@ -355,6 +375,9 @@ ptrdiff_t Canvas::getHandle() const
love::image::ImageData *Canvas::newImageData(love::image::Image *module, int slice, int x, int y, int w, int h)
{
if (!isReadable())
throw love::Exception("Canvas:newImageData cannot be called on non-readable Canvases.");
if (x < 0 || y < 0 || w <= 0 || h <= 0 || (x + w) > getPixelWidth() || (y + h) > getPixelHeight())
throw love::Exception("Invalid rectangle dimensions.");
@@ -423,7 +446,7 @@ PixelFormat Canvas::getSizedFormat(PixelFormat format)
case PIXELFORMAT_NORMAL:
if (isGammaCorrect())
return PIXELFORMAT_sRGBA8;
else if (!OpenGL::isPixelFormatSupported(PIXELFORMAT_RGBA8, true, false))
else if (!OpenGL::isPixelFormatSupported(PIXELFORMAT_RGBA8, true, true, false))
// 32-bit render targets don't have guaranteed support on GLES2.
return PIXELFORMAT_RGBA4;
else
@@ -456,7 +479,10 @@ bool Canvas::isFormatSupported(PixelFormat format)
bool supported = true;
format = getSizedFormat(format);
if (!OpenGL::isPixelFormatSupported(format, true, false))
bool depthstencil = isPixelFormatDepthStencil(format);
bool readable = !depthstencil;
if (!OpenGL::isPixelFormatSupported(format, true, readable, false))
return false;
if (checkedFormats[format])
@@ -466,28 +492,37 @@ bool Canvas::isFormatSupported(PixelFormat format)
// 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.
if (readable)
{
GLuint texture = 0;
glGenTextures(1, &texture);
gl.bindTextureToUnit(TEXTURE_2D, texture, 0, false);
GLuint texture = 0;
glGenTextures(1, &texture);
gl.bindTextureToUnit(TEXTURE_2D, texture, 0, false);
Texture::Filter f;
f.min = f.mag = Texture::FILTER_NEAREST;
gl.setTextureFilter(TEXTURE_2D, f);
Texture::Filter f;
f.min = f.mag = Texture::FILTER_NEAREST;
gl.setTextureFilter(TEXTURE_2D, f);
Texture::Wrap w;
gl.setTextureWrap(TEXTURE_2D, w);
Texture::Wrap w;
gl.setTextureWrap(TEXTURE_2D, w);
bool unusedSRGB = false;
gl.rawTexStorage(TEXTURE_2D, 1, format, unusedSRGB, 2, 2);
bool unusedSRGB = false;
OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(format, false, unusedSRGB);
GLuint fbo = 0;
supported = (createFBO(fbo, TEXTURE_2D, texture, 1, false) == GL_FRAMEBUFFER_COMPLETE);
gl.deleteFramebuffer(fbo);
glTexImage2D(GL_TEXTURE_2D, 0, fmt.internalformat, 2, 2, 0, fmt.externalformat, fmt.type, nullptr);
gl.deleteTexture(texture);
}
else
{
int samples = 0;
GLuint renderbuffer = 0;
supported = createRenderbuffer(2, 2, samples, format, renderbuffer);
GLuint fbo = 0;
supported = (createFBO(fbo, TEXTURE_2D, texture, 1, false) == GL_FRAMEBUFFER_COMPLETE);
gl.deleteFramebuffer(fbo);
gl.deleteTexture(texture);
if (supported)
glDeleteRenderbuffers(1, &renderbuffer);
}
// Cache the result so we don't do this for every isFormatSupported call.
checkedFormats[format] = true;
+8 -10
View File
@@ -56,20 +56,19 @@ public:
int getMSAA() const override
{
return actual_samples;
return actualSamples;
}
int getRequestedMSAA() const override
{
return settings.msaa;
return requestedSamples;
}
ptrdiff_t getMSAAHandle() const override
ptrdiff_t getRenderTargetHandle() const override
{
return msaa_buffer;
return renderbuffer != 0 ? renderbuffer : texture;
}
inline GLenum getStatus() const
{
return status;
@@ -87,18 +86,17 @@ public:
private:
Settings settings;
GLuint fbo;
GLuint texture;
GLuint msaa_buffer;
GLuint renderbuffer;
GLenum status;
int actual_samples;
int requestedSamples;
int actualSamples;
size_t texture_memory;
size_t textureMemory;
static bool supportedFormats[PIXELFORMAT_MAX_ENUM];
static bool checkedFormats[PIXELFORMAT_MAX_ENUM];
+147 -128
View File
@@ -169,7 +169,7 @@ void Graphics::setViewportSize(int width, int height, int pixelwidth, int pixelh
this->pixelWidth = pixelwidth;
this->pixelHeight = pixelheight;
if (states.back().renderTargets.empty())
if (!isCanvasActive())
{
// Set the viewport to top-left corner.
gl.setViewport({0, 0, pixelwidth, pixelheight});
@@ -302,8 +302,8 @@ void Graphics::unSetMode()
for (const auto &pair : framebufferObjects)
gl.deleteFramebuffer(pair.second);
for (const CachedRenderbuffer &rb : stencilBuffers)
glDeleteRenderbuffers(1, &rb.renderbuffer);
for (love::graphics::Canvas *c : stencilBuffers)
c->release();
framebufferObjects.clear();
stencilBuffers.clear();
@@ -355,6 +355,9 @@ void Graphics::flushStreamDraws()
Shader::standardShaders[Shader::STANDARD_ARRAY]->attach();
}
if (!sbstate.texture->isReadable())
throw love::Exception("Textures with non-readable formats cannot be drawn.");
if (Shader::current)
Shader::current->checkMainTextureType(textype);
}
@@ -502,28 +505,38 @@ void Graphics::setDebug(bool enable)
::printf("OpenGL debug output enabled (LOVE_GRAPHICS_DEBUG=1)\n");
}
void Graphics::setCanvas(const std::vector<RenderTarget> &rts)
void Graphics::setCanvas(const RenderTargets &rts)
{
DisplayState &state = states.back();
int ncanvases = (int) rts.size();
int ncanvases = (int) rts.colors.size();
if (ncanvases == 0)
if (ncanvases == 0 && rts.depthStencil.canvas == nullptr)
return setCanvas();
else if (ncanvases == 0)
throw love::Exception("At least one color render target is required when using a custom depth/stencil buffer.");
if (ncanvases == (int) state.renderTargets.size())
const auto &prevRTs = state.renderTargets;
if (ncanvases == (int) prevRTs.colors.size())
{
bool modified = false;
for (int i = 0; i < ncanvases; i++)
{
if (rts[i].canvas != state.renderTargets[i].canvas.get()
|| rts[i].slice != state.renderTargets[i].slice)
if (rts.colors[i].canvas != prevRTs.colors[i].canvas.get()
|| rts.colors[i].slice != prevRTs.colors[i].slice)
{
modified = true;
break;
}
}
if (!modified && rts.depthStencil.canvas == prevRTs.depthStencil.canvas
&& rts.depthStencil.slice == prevRTs.depthStencil.slice)
{
modified = true;
}
if (!modified)
return;
}
@@ -531,32 +544,53 @@ void Graphics::setCanvas(const std::vector<RenderTarget> &rts)
if (ncanvases > gl.getMaxRenderTargets())
throw love::Exception("This system can't simultaneously render to %d canvases.", ncanvases);
love::graphics::Canvas *firstcanvas = rts[0].canvas;
love::graphics::Canvas *firstcanvas = rts.colors[0].canvas;
bool multiformatsupported = Canvas::isMultiFormatMultiCanvasSupported();
PixelFormat firstformat = firstcanvas->getPixelFormat();
if (isPixelFormatDepthStencil(firstformat))
throw love::Exception("Depth/stencil format Canvases must be used with the 'depthstencil' field of the table passed into setCanvas.");
bool hasSRGBcanvas = firstformat == PIXELFORMAT_sRGBA8;
int pixelwidth = firstcanvas->getPixelWidth();
int pixelheight = firstcanvas->getPixelHeight();
for (int i = 1; i < ncanvases; i++)
{
love::graphics::Canvas *c = rts[i].canvas;
love::graphics::Canvas *c = rts.colors[i].canvas;
PixelFormat format = c->getPixelFormat();
if (c->getPixelWidth() != pixelwidth || c->getPixelHeight() != pixelheight)
throw love::Exception("All canvases in must have the same pixel dimensions.");
throw love::Exception("All canvases must have the same pixel dimensions.");
if (!multiformatsupported && c->getPixelFormat() != firstformat)
if (!multiformatsupported && format != firstformat)
throw love::Exception("This system doesn't support multi-canvas rendering with different canvas formats.");
if (c->getRequestedMSAA() != firstcanvas->getRequestedMSAA())
throw love::Exception("All Canvases in must have the same MSAA value.");
throw love::Exception("All Canvases must have the same MSAA value.");
if (c->getPixelFormat() == PIXELFORMAT_sRGBA8)
if (isPixelFormatDepthStencil(format))
throw love::Exception("Depth/stencil format Canvases must be used with the 'depthstencil' field of the table passed into setCanvas.");
if (format == PIXELFORMAT_sRGBA8)
hasSRGBcanvas = true;
}
if (rts.depthStencil.canvas != nullptr)
{
love::graphics::Canvas *c = rts.depthStencil.canvas;
if (!isPixelFormatDepthStencil(c->getPixelFormat()))
throw love::Exception("Only depth/stencil format Canvases can be used with the 'depthstencil' field of the table passed into setCanvas.");
if (c->getPixelWidth() != pixelwidth || c->getPixelHeight() != pixelheight)
throw love::Exception("All canvases must have the same pixel dimensions.");
if (c->getRequestedMSAA() != firstcanvas->getRequestedMSAA())
throw love::Exception("All Canvases must have the same MSAA value.");
}
OpenGL::TempDebugGroup debuggroup("setCanvas(...)");
endPass();
@@ -583,13 +617,15 @@ void Graphics::setCanvas(const std::vector<RenderTarget> &rts)
gl.setFramebufferSRGB(false);
}
std::vector<RenderTargetStrongRef> canvasrefs;
canvasrefs.reserve(rts.size());
RenderTargetsStrongRef refs;
refs.colors.reserve(rts.colors.size());
for (auto c : rts)
canvasrefs.emplace_back(c.canvas, c.slice);
for (auto c : rts.colors)
refs.colors.emplace_back(c.canvas, c.slice);
std::swap(state.renderTargets, canvasrefs);
refs.depthStencil = RenderTargetStrongRef(rts.depthStencil.canvas, rts.depthStencil.slice);
std::swap(state.renderTargets, refs);
canvasSwitchCount++;
}
@@ -598,14 +634,14 @@ void Graphics::setCanvas()
{
DisplayState &state = states.back();
if (state.renderTargets.empty())
if (state.renderTargets.colors.empty() && state.renderTargets.depthStencil.canvas == nullptr)
return;
OpenGL::TempDebugGroup debuggroup("setCanvas()");
endPass();
state.renderTargets.clear();
state.renderTargets = RenderTargetsStrongRef();
gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, gl.getDefaultFBO());
@@ -635,21 +671,25 @@ void Graphics::endPass()
{
flushStreamDraws();
// Discard the stencil buffer.
discard({}, true);
auto &rts = states.back().renderTargets;
auto &canvases = states.back().renderTargets;
// Discard the stencil buffer if we're using an internal cached one.
if (rts.depthStencil.canvas.get() == nullptr)
discard({}, true);
// Resolve MSAA buffers. MSAA is only supported for 2D render targets so we
// don't have to worry about resolving to slices.
if (canvases.size() > 0 && canvases[0].canvas->getMSAA() > 1)
if (rts.colors.size() > 0 && rts.colors[0].canvas->getMSAA() > 1)
{
int w = canvases[0].canvas->getPixelWidth();
int h = canvases[0].canvas->getPixelHeight();
int w = rts.colors[0].canvas->getPixelWidth();
int h = rts.colors[0].canvas->getPixelHeight();
for (int i = 0; i < (int) canvases.size(); i++)
for (int i = 0; i < (int) rts.colors.size(); i++)
{
Canvas *c = (Canvas *) canvases[i].canvas.get();
Canvas *c = (Canvas *) rts.colors[i].canvas.get();
if (!c->isReadable())
continue;
glReadBuffer(GL_COLOR_ATTACHMENT0 + i);
@@ -685,7 +725,7 @@ void Graphics::clear(const std::vector<OptionalColorf> &colors)
if (colors.size() == 0)
return;
int ncanvases = (int) states.back().renderTargets.size();
int ncanvases = (int) states.back().renderTargets.colors.size();
int ncolors = std::min((int) colors.size(), ncanvases);
if (ncolors <= 1 && ncanvases <= 1)
@@ -767,7 +807,7 @@ void Graphics::discard(OpenGL::FramebufferTarget target, const std::vector<bool>
attachments.reserve(colorbuffers.size());
// glDiscardFramebuffer uses different attachment enums for the default FBO.
if (states.back().renderTargets.empty() && gl.getDefaultFBO() == 0)
if (!isCanvasActive() && gl.getDefaultFBO() == 0)
{
if (colorbuffers.size() > 0 && colorbuffers[0])
attachments.push_back(GL_COLOR);
@@ -780,7 +820,7 @@ void Graphics::discard(OpenGL::FramebufferTarget target, const std::vector<bool>
}
else
{
int rendertargetcount = std::max((int) states.back().renderTargets.size(), 1);
int rendertargetcount = std::max((int) states.back().renderTargets.colors.size(), 1);
for (int i = 0; i < (int) colorbuffers.size(); i++)
{
@@ -802,11 +842,18 @@ void Graphics::discard(OpenGL::FramebufferTarget target, const std::vector<bool>
glDiscardFramebufferEXT(gltarget, (GLint) attachments.size(), &attachments[0]);
}
void Graphics::bindCachedFBO(const std::vector<RenderTarget> &targets)
void Graphics::bindCachedFBO(const RenderTargets &targets)
{
int ntargets = (int) targets.size();
uint32 hash = XXH32(&targets[0], sizeof(RenderTarget) * ntargets, 0);
RenderTarget hashtargets[MAX_COLOR_RENDER_TARGETS + 1];
int hashcount = 0;
for (int i = 0; i < (int) targets.colors.size(); i++)
hashtargets[hashcount++] = targets.colors[i];
if (targets.depthStencil.canvas != nullptr)
hashtargets[hashcount++] = targets.depthStencil;
uint32 hash = XXH32(hashtargets, sizeof(RenderTarget) * hashcount, 0);
GLuint fbo = framebufferObjects[hash];
if (fbo != 0)
@@ -815,47 +862,66 @@ void Graphics::bindCachedFBO(const std::vector<RenderTarget> &targets)
}
else
{
int w = targets[0].canvas->getPixelWidth();
int h = targets[0].canvas->getPixelHeight();
int msaa = std::max(targets[0].canvas->getMSAA(), 1);
int w = targets.colors[0].canvas->getPixelWidth();
int h = targets.colors[0].canvas->getPixelHeight();
int msaa = targets.colors[0].canvas->getMSAA();
int reqmsaa = targets.colors[0].canvas->getRequestedMSAA();
RenderTarget depthstencil = targets.depthStencil;
if (depthstencil.canvas == nullptr)
{
depthstencil.canvas = getCachedStencilBuffer(w, h, reqmsaa);
depthstencil.slice = 0;
}
glGenFramebuffers(1, &fbo);
gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, fbo);
int ncolortargets = 0;
GLenum drawbuffers[MAX_COLOR_RENDER_TARGETS];
for (int i = 0; i < ntargets; i++)
auto attachCanvas = [&](const RenderTarget &rt)
{
drawbuffers[i] = GL_COLOR_ATTACHMENT0 + i;
bool renderbuffer = msaa > 1 || !rt.canvas->isReadable();
bool srgb = false;
OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(rt.canvas->getPixelFormat(), renderbuffer, srgb);
if (msaa > 1)
if (fmt.framebufferAttachments[0] == GL_COLOR_ATTACHMENT0)
{
GLuint rbo = (GLuint) targets[i].canvas->getMSAAHandle();
glFramebufferRenderbuffer(GL_FRAMEBUFFER, drawbuffers[i], GL_RENDERBUFFER, rbo);
fmt.framebufferAttachments[0] = GL_COLOR_ATTACHMENT0 + ncolortargets;
drawbuffers[ncolortargets] = fmt.framebufferAttachments[0];
ncolortargets++;
}
else
GLuint handle = (GLuint) rt.canvas->getRenderTargetHandle();
for (GLenum attachment : fmt.framebufferAttachments)
{
GLuint tex = (GLuint) targets[i].canvas->getHandle();
TextureType textype = targets[i].canvas->getTextureType();
if (attachment == GL_NONE)
continue;
else if (renderbuffer)
glFramebufferRenderbuffer(GL_FRAMEBUFFER, attachment, GL_RENDERBUFFER, handle);
else
{
TextureType textype = rt.canvas->getTextureType();
int layer = textype == TEXTURE_CUBE ? 0 : targets[i].slice;
int face = textype == TEXTURE_CUBE ? targets[i].slice : 0;
int layer = textype == TEXTURE_CUBE ? 0 : rt.slice;
int face = textype == TEXTURE_CUBE ? rt.slice : 0;
gl.framebufferTexture(drawbuffers[i], textype, tex, 0, layer, face);
gl.framebufferTexture(attachment, textype, handle, 0, layer, face);
}
}
}
};
if (ntargets > 1)
glDrawBuffers(ntargets, drawbuffers);
for (const auto &rt : targets.colors)
attachCanvas(rt);
GLuint stencil = attachCachedStencilBuffer(w, h, targets[0].canvas->getRequestedMSAA());
if (depthstencil.canvas != nullptr)
attachCanvas(depthstencil);
if (stencil == 0)
{
gl.deleteFramebuffer(fbo);
gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, gl.getDefaultFBO());
throw love::Exception("Could not create stencil buffer!");
}
if (ncolortargets > 1)
glDrawBuffers(ncolortargets, drawbuffers);
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
@@ -870,80 +936,33 @@ void Graphics::bindCachedFBO(const std::vector<RenderTarget> &targets)
}
}
GLuint Graphics::attachCachedStencilBuffer(int w, int h, int samples)
love::graphics::Canvas *Graphics::getCachedStencilBuffer(int w, int h, int samples)
{
samples = samples == 1 ? 0 : samples;
love::graphics::Canvas *canvas = nullptr;
for (const CachedRenderbuffer &rb : stencilBuffers)
for (love::graphics::Canvas *c : stencilBuffers)
{
if (rb.w == w && rb.h == h && rb.samples == samples)
if (c->getPixelWidth() == w && c->getPixelHeight() == h && c->getRequestedMSAA() == samples)
{
// Attach the buffer to the framebuffer object.
for (GLenum attachment : rb.attachments)
{
if (attachment != GL_NONE)
glFramebufferRenderbuffer(GL_FRAMEBUFFER, attachment, GL_RENDERBUFFER, rb.renderbuffer);
}
return rb.renderbuffer;
canvas = c;
break;
}
}
OpenGL::TempDebugGroup debuggroup("Create cached stencil buffer");
CachedRenderbuffer rb;
rb.w = w;
rb.h = h;
rb.samples = samples;
rb.attachments[0] = GL_STENCIL_ATTACHMENT;
rb.attachments[1] = GL_NONE;
GLenum format = GL_STENCIL_INDEX8;
// Prefer a combined depth/stencil buffer.
if (GLAD_ES_VERSION_3_0 || GLAD_VERSION_3_0 || GLAD_ARB_framebuffer_object)
if (canvas == nullptr)
{
format = GL_DEPTH24_STENCIL8;
rb.attachments[0] = GL_DEPTH_STENCIL_ATTACHMENT;
}
else if (GLAD_EXT_packed_depth_stencil || GLAD_OES_packed_depth_stencil)
{
format = GL_DEPTH24_STENCIL8;
rb.attachments[0] = GL_DEPTH_ATTACHMENT;
rb.attachments[1] = GL_STENCIL_ATTACHMENT;
Canvas::Settings settings;
settings.format = PIXELFORMAT_STENCIL8;
settings.width = w;
settings.height = h;
settings.msaa = samples;
canvas = newCanvas(settings);
stencilBuffers.push_back(canvas);
}
glGenRenderbuffers(1, &rb.renderbuffer);
glBindRenderbuffer(GL_RENDERBUFFER, rb.renderbuffer);
if (rb.samples > 1)
glRenderbufferStorageMultisample(GL_RENDERBUFFER, rb.samples, format, rb.w, rb.h);
else
glRenderbufferStorage(GL_RENDERBUFFER, format, rb.w, rb.h);
// Attach the buffer to the framebuffer object.
for (GLenum attachment : rb.attachments)
{
if (attachment != GL_NONE)
glFramebufferRenderbuffer(GL_FRAMEBUFFER, attachment, GL_RENDERBUFFER, rb.renderbuffer);
}
glBindRenderbuffer(GL_RENDERBUFFER, 0);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
{
glDeleteRenderbuffers(1, &rb.renderbuffer);
rb.renderbuffer = 0;
}
if (rb.renderbuffer != 0)
{
glClear(GL_STENCIL_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
stencilBuffers.push_back(rb);
}
return rb.renderbuffer;
return canvas;
}
void Graphics::present(void *screenshotCallbackData)
@@ -951,7 +970,7 @@ void Graphics::present(void *screenshotCallbackData)
if (!isActive())
return;
if (!states.back().renderTargets.empty())
if (isCanvasActive())
throw love::Exception("present cannot be called while a Canvas is active.");
endPass();
@@ -1083,7 +1102,7 @@ void Graphics::setScissor(const Rect &rect)
glrect.h = (int) (rect.h * density);
// OpenGL's reversed y-coordinate is compensated for in OpenGL::setScissor.
gl.setScissor(glrect, !state.renderTargets.empty());
gl.setScissor(glrect, isCanvasActive());
state.scissor = true;
state.scissorRect = rect;
@@ -1100,7 +1119,7 @@ void Graphics::setScissor()
void Graphics::drawToStencilBuffer(StencilAction action, int value)
{
if (states.back().renderTargets.empty() && !windowHasStencil)
if (!isCanvasActive() && !windowHasStencil)
throw love::Exception("The window must have stenciling enabled to draw to the main screen's stencil buffer.");
flushStreamDraws();
@@ -1161,7 +1180,7 @@ void Graphics::stopDrawToStencilBuffer()
void Graphics::setStencilTest(CompareMode compare, int value)
{
if (compare != COMPARE_ALWAYS && states.back().renderTargets.empty() && !windowHasStencil)
if (compare != COMPARE_ALWAYS && !isCanvasActive() && !windowHasStencil)
throw love::Exception("The window must have stenciling enabled to use setStencilTest on the main screen.");
DisplayState &state = states.back();
+4 -13
View File
@@ -97,7 +97,7 @@ public:
void setColor(Colorf c) override;
void setCanvas(const std::vector<RenderTarget> &rts) override;
void setCanvas(const RenderTargets &rts) override;
void setCanvas() override;
void setScissor(const Rect &rect) override;
@@ -131,26 +131,17 @@ public:
private:
struct CachedRenderbuffer
{
int w;
int h;
int samples;
GLenum attachments[2];
GLuint renderbuffer;
};
love::graphics::StreamBuffer *newStreamBuffer(BufferType type, size_t size) override;
void endPass();
void bindCachedFBO(const std::vector<RenderTarget> &targets);
void bindCachedFBO(const RenderTargets &targets);
void discard(OpenGL::FramebufferTarget target, const std::vector<bool> &colorbuffers, bool depthstencil);
GLuint attachCachedStencilBuffer(int w, int h, int samples);
love::graphics::Canvas *getCachedStencilBuffer(int w, int h, int samples);
void setDebug(bool enable);
std::unordered_map<uint32, GLuint> framebufferObjects;
std::vector<CachedRenderbuffer> stencilBuffers;
std::vector<love::graphics::Canvas *> stencilBuffers;
QuadIndices *quadIndices;
+2 -2
View File
@@ -237,7 +237,7 @@ bool Image::loadVolatile()
OpenGL::TempDebugGroup debuggroup("Image load");
if (!OpenGL::isPixelFormatSupported(format, false, sRGB))
if (!OpenGL::isPixelFormatSupported(format, false, true, sRGB))
{
const char *str;
if (love::getConstant(format, str))
@@ -511,7 +511,7 @@ Image::MipmapsType Image::getMipmapsType() const
bool Image::isFormatSupported(PixelFormat pixelformat)
{
return OpenGL::isPixelFormatSupported(pixelformat, false, false);
return OpenGL::isPixelFormatSupported(pixelformat, false, true, false);
}
bool Image::hasSRGBSupport()
+1 -1
View File
@@ -100,7 +100,7 @@ void Mesh::drawInstanced(love::graphics::Graphics *gfx, const love::Matrix4 &m,
throw love::Exception("Instancing is not supported on this system.");
if (Shader::current && texture.get())
Shader::current->checkMainTextureType(texture->getTextureType());
Shader::current->checkMainTexture(texture);
gfx->flushStreamDraws();
+33 -1
View File
@@ -1173,6 +1173,9 @@ OpenGL::TextureFormat OpenGL::convertPixelFormat(PixelFormat pixelformat, bool r
{
TextureFormat f;
f.framebufferAttachments[0] = GL_COLOR_ATTACHMENT0;
f.framebufferAttachments[1] = GL_NONE;
if (pixelformat == PIXELFORMAT_RGBA8 && isSRGB)
pixelformat = PIXELFORMAT_sRGBA8;
else if (pixelformat == PIXELFORMAT_ETC1)
@@ -1314,6 +1317,32 @@ OpenGL::TextureFormat OpenGL::convertPixelFormat(PixelFormat pixelformat, bool r
f.type = GL_UNSIGNED_INT_10F_11F_11F_REV;
break;
case PIXELFORMAT_STENCIL8:
// Prefer a combined depth/stencil buffer due to driver issues.
if (GLAD_ES_VERSION_3_0 || GLAD_VERSION_3_0 || GLAD_ARB_framebuffer_object)
{
f.internalformat = GL_DEPTH24_STENCIL8;
f.externalformat = GL_DEPTH_STENCIL;
f.type = GL_UNSIGNED_INT_24_8;
f.framebufferAttachments[0] = GL_DEPTH_STENCIL_ATTACHMENT;
}
else if (GLAD_EXT_packed_depth_stencil || GLAD_OES_packed_depth_stencil)
{
f.internalformat = GL_DEPTH24_STENCIL8;
f.externalformat = GL_DEPTH_STENCIL;
f.type = GL_UNSIGNED_INT_24_8;
f.framebufferAttachments[0] = GL_DEPTH_ATTACHMENT;
f.framebufferAttachments[1] = GL_STENCIL_ATTACHMENT;
}
else
{
f.internalformat = GL_STENCIL_INDEX8;
f.externalformat = GL_STENCIL;
f.type = GL_UNSIGNED_BYTE;
f.framebufferAttachments[0] = GL_STENCIL_ATTACHMENT;
}
break;
case PIXELFORMAT_DXT1:
f.internalformat = isSRGB ? GL_COMPRESSED_SRGB_S3TC_DXT1_EXT : GL_COMPRESSED_RGB_S3TC_DXT1_EXT;
break;
@@ -1454,7 +1483,7 @@ OpenGL::TextureFormat OpenGL::convertPixelFormat(PixelFormat pixelformat, bool r
return f;
}
bool OpenGL::isPixelFormatSupported(PixelFormat pixelformat, bool rendertarget, bool isSRGB)
bool OpenGL::isPixelFormatSupported(PixelFormat pixelformat, bool rendertarget, bool readable, bool isSRGB)
{
if (rendertarget && isPixelFormatCompressed(pixelformat))
return false;
@@ -1548,6 +1577,9 @@ bool OpenGL::isPixelFormatSupported(PixelFormat pixelformat, bool rendertarget,
else
return GLAD_VERSION_3_0 || GLAD_EXT_packed_float || GLAD_APPLE_texture_packed_float;
case PIXELFORMAT_STENCIL8:
return rendertarget && !readable;
case PIXELFORMAT_DXT1:
return GLAD_EXT_texture_compression_s3tc || GLAD_EXT_texture_compression_dxt1;
case PIXELFORMAT_DXT3:
+4 -1
View File
@@ -96,6 +96,9 @@ public:
GLenum externalformat = 0;
GLenum type = 0;
// For depth/stencil formats.
GLenum framebufferAttachments[2];
bool swizzled = false;
GLint swizzle[4];
};
@@ -379,7 +382,7 @@ public:
static TextureFormat convertPixelFormat(PixelFormat pixelformat, bool renderbuffer, bool &isSRGB);
static bool isTexStorageSupported();
static bool isPixelFormatSupported(PixelFormat pixelformat, bool rendertarget, bool isSRGB);
static bool isPixelFormatSupported(PixelFormat pixelformat, bool rendertarget, bool readable, bool isSRGB);
static bool hasTextureFilteringSupport(PixelFormat pixelformat);
static const char *errorString(GLenum errorcode);
@@ -58,7 +58,7 @@ void ParticleSystem::draw(Graphics *gfx, const Matrix4 &m)
return;
if (Shader::current && texture.get())
Shader::current->checkMainTextureType(texture->getTextureType());
Shader::current->checkMainTexture(texture);
OpenGL::TempDebugGroup debuggroup("ParticleSystem draw");
+1 -1
View File
@@ -677,7 +677,7 @@ void Shader::sendTextures(const UniformInfo *info, Texture **textures, int count
{
if (textures[i] != nullptr)
{
if (textures[i]->getTextureType() != info->textureType)
if (textures[i]->getTextureType() != info->textureType || !textures[i]->isReadable())
continue;
textures[i]->retain();
+1 -1
View File
@@ -76,7 +76,7 @@ void SpriteBatch::draw(Graphics *gfx, const Matrix4 &m)
}
if (Shader::current)
Shader::current->checkMainTextureType(texture->getTextureType());
Shader::current->checkMainTexture(texture);
}
OpenGL::TempDebugGroup debuggroup("SpriteBatch draw");