mirror of
https://github.com/love2d/love.git
synced 2026-08-16 08:11:02 +02:00
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:
@@ -55,7 +55,7 @@ public:
|
||||
|
||||
virtual int getMSAA() const = 0;
|
||||
virtual int getRequestedMSAA() const = 0;
|
||||
virtual ptrdiff_t getMSAAHandle() const = 0;
|
||||
virtual ptrdiff_t getRenderTargetHandle() const = 0;
|
||||
|
||||
void draw(Graphics *gfx, Quad *q, const Matrix4 &t) override;
|
||||
void drawLayer(Graphics *gfx, int layer, Quad *q, const Matrix4 &t) override;
|
||||
|
||||
@@ -187,9 +187,9 @@ int Graphics::getPixelHeight() const
|
||||
|
||||
double Graphics::getCurrentPixelDensity() const
|
||||
{
|
||||
if (states.back().renderTargets.size() > 0)
|
||||
if (states.back().renderTargets.colors.size() > 0)
|
||||
{
|
||||
love::graphics::Canvas *c = states.back().renderTargets[0].canvas;
|
||||
Canvas *c = states.back().renderTargets.colors[0].canvas;
|
||||
return (double) c->getPixelHeight() / (double) c->getHeight();
|
||||
}
|
||||
|
||||
@@ -291,19 +291,28 @@ void Graphics::restoreStateChecked(const DisplayState &s)
|
||||
setFont(s.font.get());
|
||||
setShader(s.shader.get());
|
||||
|
||||
bool canvaseschanged = s.renderTargets.size() != cur.renderTargets.size();
|
||||
const auto &sRTs = s.renderTargets;
|
||||
const auto &curRTs = cur.renderTargets;
|
||||
|
||||
bool canvaseschanged = sRTs.colors.size() != curRTs.colors.size();
|
||||
if (!canvaseschanged)
|
||||
{
|
||||
for (size_t i = 0; i < s.renderTargets.size() && i < cur.renderTargets.size(); i++)
|
||||
for (size_t i = 0; i < sRTs.colors.size() && i < curRTs.colors.size(); i++)
|
||||
{
|
||||
const auto &rt1 = s.renderTargets[i];
|
||||
const auto &rt2 = cur.renderTargets[i];
|
||||
const auto &rt1 = sRTs.colors[i];
|
||||
const auto &rt2 = curRTs.colors[i];
|
||||
if (rt1.canvas.get() != rt2.canvas.get() || rt1.slice != rt2.slice)
|
||||
{
|
||||
canvaseschanged = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!canvaseschanged && (sRTs.depthStencil.canvas.get() != curRTs.depthStencil.canvas.get()
|
||||
|| sRTs.depthStencil.slice != curRTs.depthStencil.slice))
|
||||
{
|
||||
canvaseschanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (canvaseschanged)
|
||||
@@ -401,45 +410,58 @@ void Graphics::setCanvas(RenderTarget rt)
|
||||
if (rt.canvas == nullptr)
|
||||
return setCanvas();
|
||||
|
||||
std::vector<RenderTarget> rts = {rt};
|
||||
RenderTargets rts;
|
||||
rts.colors.push_back(rt);
|
||||
|
||||
setCanvas(rts);
|
||||
}
|
||||
|
||||
void Graphics::setCanvas(const std::vector<RenderTargetStrongRef> &rts)
|
||||
void Graphics::setCanvas(const RenderTargetsStrongRef &rts)
|
||||
{
|
||||
std::vector<RenderTarget> canvaslist;
|
||||
canvaslist.reserve(rts.size());
|
||||
RenderTargets targets;
|
||||
targets.colors.reserve(rts.colors.size());
|
||||
|
||||
for (const auto &rt : rts)
|
||||
canvaslist.emplace_back(rt.canvas.get(), rt.slice);
|
||||
for (const auto &rt : rts.colors)
|
||||
targets.colors.emplace_back(rt.canvas.get(), rt.slice);
|
||||
|
||||
return setCanvas(canvaslist);
|
||||
targets.depthStencil = RenderTarget(rts.depthStencil.canvas, rts.depthStencil.slice);
|
||||
|
||||
return setCanvas(targets);
|
||||
}
|
||||
|
||||
std::vector<Graphics::RenderTarget> Graphics::getCanvas() const
|
||||
Graphics::RenderTargets Graphics::getCanvas() const
|
||||
{
|
||||
std::vector<RenderTarget> rts;
|
||||
rts.reserve(states.back().renderTargets.size());
|
||||
const auto &curRTs = states.back().renderTargets;
|
||||
|
||||
for (const auto &rt : states.back().renderTargets)
|
||||
rts.emplace_back(rt.canvas.get(), rt.slice);
|
||||
RenderTargets rts;
|
||||
rts.colors.reserve(curRTs.colors.size());
|
||||
|
||||
for (const auto &rt : curRTs.colors)
|
||||
rts.colors.emplace_back(rt.canvas.get(), rt.slice);
|
||||
|
||||
rts.depthStencil = RenderTarget(curRTs.depthStencil.canvas, curRTs.depthStencil.slice);
|
||||
|
||||
return rts;
|
||||
}
|
||||
|
||||
bool Graphics::isCanvasActive() const
|
||||
{
|
||||
return !states.back().renderTargets.empty();
|
||||
return !states.back().renderTargets.colors.empty();
|
||||
}
|
||||
|
||||
bool Graphics::isCanvasActive(love::graphics::Canvas *canvas) const
|
||||
{
|
||||
for (const auto &rt : states.back().renderTargets)
|
||||
const auto &curRTs = states.back().renderTargets;
|
||||
|
||||
for (const auto &rt : curRTs.colors)
|
||||
{
|
||||
if (rt.canvas.get() == canvas)
|
||||
return true;
|
||||
}
|
||||
|
||||
if (curRTs.depthStencil.canvas.get() == canvas)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -315,13 +315,18 @@ public:
|
||||
|
||||
struct RenderTarget
|
||||
{
|
||||
Canvas *canvas = nullptr;
|
||||
int slice = 0;
|
||||
Canvas *canvas;
|
||||
int slice;
|
||||
|
||||
RenderTarget(Canvas *canvas, int slice = 0)
|
||||
: canvas(canvas)
|
||||
, slice(slice)
|
||||
{}
|
||||
|
||||
RenderTarget()
|
||||
: canvas(nullptr)
|
||||
, slice(0)
|
||||
{}
|
||||
};
|
||||
|
||||
struct RenderTargetStrongRef
|
||||
@@ -335,6 +340,26 @@ public:
|
||||
{}
|
||||
};
|
||||
|
||||
struct RenderTargets
|
||||
{
|
||||
std::vector<RenderTarget> colors;
|
||||
RenderTarget depthStencil;
|
||||
|
||||
RenderTargets()
|
||||
: depthStencil(nullptr)
|
||||
{}
|
||||
};
|
||||
|
||||
struct RenderTargetsStrongRef
|
||||
{
|
||||
std::vector<RenderTargetStrongRef> colors;
|
||||
RenderTargetStrongRef depthStencil;
|
||||
|
||||
RenderTargetsStrongRef()
|
||||
: depthStencil(nullptr)
|
||||
{}
|
||||
};
|
||||
|
||||
Graphics();
|
||||
virtual ~Graphics();
|
||||
|
||||
@@ -456,11 +481,11 @@ public:
|
||||
Shader *getShader() const;
|
||||
|
||||
void setCanvas(RenderTarget rt);
|
||||
virtual void setCanvas(const std::vector<RenderTarget> &rts) = 0;
|
||||
void setCanvas(const std::vector<RenderTargetStrongRef> &rts);
|
||||
virtual void setCanvas(const RenderTargets &rts) = 0;
|
||||
void setCanvas(const RenderTargetsStrongRef &rts);
|
||||
virtual void setCanvas() = 0;
|
||||
|
||||
std::vector<RenderTarget> getCanvas() const;
|
||||
RenderTargets getCanvas() const;
|
||||
bool isCanvasActive() const;
|
||||
bool isCanvasActive(Canvas *canvas) const;
|
||||
|
||||
@@ -813,7 +838,7 @@ protected:
|
||||
StrongRef<Font> font;
|
||||
StrongRef<Shader> shader;
|
||||
|
||||
std::vector<RenderTargetStrongRef> renderTargets;
|
||||
RenderTargetsStrongRef renderTargets;
|
||||
|
||||
ColorMask colorMask = ColorMask(true, true, true, true);
|
||||
|
||||
|
||||
@@ -206,6 +206,14 @@ void Shader::checkMainTextureType(TextureType textype) const
|
||||
}
|
||||
}
|
||||
|
||||
void Shader::checkMainTexture(Texture *texture) const
|
||||
{
|
||||
if (!texture->isReadable())
|
||||
throw love::Exception("Textures with non-readable formats cannot be sampled from in a shader.");
|
||||
|
||||
checkMainTextureType(texture->getTextureType());
|
||||
}
|
||||
|
||||
bool Shader::validate(Graphics *gfx, bool gles, const ShaderSource &source, bool checkWithDefaults, std::string &err)
|
||||
{
|
||||
if (source.vertex.empty() && source.pixel.empty())
|
||||
@@ -242,7 +250,7 @@ bool Shader::validate(Graphics *gfx, bool gles, const ShaderSource &source, bool
|
||||
|
||||
if (!s.parse(&defaultTBuiltInResource, defaultversion, defaultprofile, forcedefault, forwardcompat, EShMsgSuppressWarnings))
|
||||
{
|
||||
const char *stagename;
|
||||
const char *stagename = "unknown";
|
||||
getConstant(stage, stagename);
|
||||
err = "Error validating " + std::string(stagename) + " shader:\n\n"
|
||||
+ std::string(s.getInfoLog()) + "\n" + std::string(s.getInfoDebugLog());
|
||||
|
||||
@@ -190,6 +190,7 @@ public:
|
||||
|
||||
TextureType getMainTextureType() const;
|
||||
void checkMainTextureType(TextureType textype) const;
|
||||
void checkMainTexture(Texture *texture) const;
|
||||
|
||||
virtual ptrdiff_t getHandle() const = 0;
|
||||
|
||||
|
||||
@@ -51,6 +51,7 @@ float Texture::defaultMipmapSharpness = 0.0f;
|
||||
Texture::Texture(TextureType texType)
|
||||
: texType(texType)
|
||||
, format(PIXELFORMAT_UNKNOWN)
|
||||
, readable(true)
|
||||
, width(0)
|
||||
, height(0)
|
||||
, depth(1)
|
||||
@@ -84,6 +85,11 @@ PixelFormat Texture::getPixelFormat() const
|
||||
return format;
|
||||
}
|
||||
|
||||
bool Texture::isReadable() const
|
||||
{
|
||||
return readable;
|
||||
}
|
||||
|
||||
void Texture::draw(Graphics *gfx, const Matrix4 &m)
|
||||
{
|
||||
draw(gfx, quad, m);
|
||||
@@ -93,6 +99,9 @@ void Texture::draw(Graphics *gfx, Quad *q, const Matrix4 &localTransform)
|
||||
{
|
||||
using namespace vertex;
|
||||
|
||||
if (!readable)
|
||||
throw love::Exception("Textures with non-readable formats cannot be drawn.");
|
||||
|
||||
if (texType == TEXTURE_2D_ARRAY)
|
||||
{
|
||||
drawLayer(gfx, q->getLayer(), q, localTransform);
|
||||
@@ -132,6 +141,9 @@ void Texture::drawLayer(Graphics *gfx, int layer, Quad *q, const Matrix4 &m)
|
||||
{
|
||||
using namespace vertex;
|
||||
|
||||
if (!readable)
|
||||
throw love::Exception("Textures with non-readable formats cannot be drawn.");
|
||||
|
||||
if (texType != TEXTURE_2D_ARRAY)
|
||||
throw love::Exception("drawLayer can only be used with Array Textures!");
|
||||
|
||||
|
||||
@@ -120,6 +120,8 @@ public:
|
||||
TextureType getTextureType() const;
|
||||
PixelFormat getPixelFormat() const;
|
||||
|
||||
bool isReadable() const;
|
||||
|
||||
int getWidth() const;
|
||||
int getHeight() const;
|
||||
int getDepth() const;
|
||||
@@ -166,6 +168,7 @@ protected:
|
||||
TextureType texType;
|
||||
|
||||
PixelFormat format;
|
||||
bool readable;
|
||||
|
||||
int width;
|
||||
int height;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -57,11 +57,14 @@ int w_Canvas_renderTo(lua_State *L)
|
||||
if (graphics)
|
||||
{
|
||||
// Save the current render targets so we can restore them when we're done.
|
||||
std::vector<Graphics::RenderTarget> oldtargets = graphics->getCanvas();
|
||||
Graphics::RenderTargets oldtargets = graphics->getCanvas();
|
||||
|
||||
for (auto c : oldtargets)
|
||||
for (auto c : oldtargets.colors)
|
||||
c.canvas->retain();
|
||||
|
||||
if (oldtargets.depthStencil.canvas != nullptr)
|
||||
oldtargets.depthStencil.canvas->retain();
|
||||
|
||||
luax_catchexcept(L, [&](){ graphics->setCanvas(rt); });
|
||||
|
||||
lua_settop(L, 2); // make sure the function is on top of the stack
|
||||
@@ -69,9 +72,12 @@ int w_Canvas_renderTo(lua_State *L)
|
||||
|
||||
graphics->setCanvas(oldtargets);
|
||||
|
||||
for (auto c : oldtargets)
|
||||
for (auto c : oldtargets.colors)
|
||||
c.canvas->release();
|
||||
|
||||
if (oldtargets.depthStencil.canvas != nullptr)
|
||||
oldtargets.depthStencil.canvas->release();
|
||||
|
||||
if (status != 0)
|
||||
return lua_error(L);
|
||||
}
|
||||
|
||||
@@ -124,7 +124,7 @@ int w_discard(lua_State *L)
|
||||
else
|
||||
{
|
||||
bool discardcolor = luax_optboolean(L, 1, true);
|
||||
size_t numbuffers = std::max((size_t) 1, instance()->getCanvas().size());
|
||||
size_t numbuffers = std::max((size_t) 1, instance()->getCanvas().colors.size());
|
||||
colorbuffers = std::vector<bool>(numbuffers, discardcolor);
|
||||
}
|
||||
|
||||
@@ -201,6 +201,21 @@ int w_getPixelDensity(lua_State *L)
|
||||
return 1;
|
||||
}
|
||||
|
||||
static Graphics::RenderTarget checkRenderTarget(lua_State *L, int idx)
|
||||
{
|
||||
lua_rawgeti(L, idx, 1);
|
||||
Graphics::RenderTarget target(luax_checkcanvas(L, -1), 0);
|
||||
lua_pop(L, 1);
|
||||
|
||||
TextureType type = target.canvas->getTextureType();
|
||||
if (type == TEXTURE_2D_ARRAY || type == TEXTURE_VOLUME)
|
||||
target.slice = luax_checkintflag(L, idx, "layer") - 1;
|
||||
else if (type == TEXTURE_CUBE)
|
||||
target.slice = luax_checkintflag(L, idx, "face") - 1;
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
int w_setCanvas(lua_State *L)
|
||||
{
|
||||
// Disable stencil writes.
|
||||
@@ -214,7 +229,7 @@ int w_setCanvas(lua_State *L)
|
||||
}
|
||||
|
||||
bool is_table = lua_istable(L, 1);
|
||||
std::vector<Graphics::RenderTarget> targets;
|
||||
Graphics::RenderTargets targets;
|
||||
|
||||
if (is_table)
|
||||
{
|
||||
@@ -227,29 +242,24 @@ int w_setCanvas(lua_State *L)
|
||||
lua_rawgeti(L, 1, i);
|
||||
|
||||
if (table_of_tables)
|
||||
{
|
||||
lua_rawgeti(L, -1, 1);
|
||||
Graphics::RenderTarget target(luax_checkcanvas(L, -1), 0);
|
||||
lua_pop(L, 1);
|
||||
|
||||
TextureType type = target.canvas->getTextureType();
|
||||
if (type == TEXTURE_2D_ARRAY || type == TEXTURE_VOLUME)
|
||||
target.slice = luax_checkintflag(L, -1, "layer") - 1;
|
||||
else if (type == TEXTURE_CUBE)
|
||||
target.slice = luax_checkintflag(L, -1, "face") - 1;
|
||||
|
||||
targets.push_back(target);
|
||||
}
|
||||
targets.colors.push_back(checkRenderTarget(L, -1));
|
||||
else
|
||||
{
|
||||
targets.emplace_back(luax_checkcanvas(L, -1), 0);
|
||||
targets.colors.emplace_back(luax_checkcanvas(L, -1), 0);
|
||||
|
||||
if (targets.back().canvas->getTextureType() != TEXTURE_2D)
|
||||
if (targets.colors.back().canvas->getTextureType() != TEXTURE_2D)
|
||||
return luaL_error(L, "The table-of-tables variant of setCanvas must be used with non-2D Canvases.");
|
||||
}
|
||||
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
|
||||
lua_getfield(L, 1, "depthstencil");
|
||||
if (lua_istable(L, -1))
|
||||
targets.depthStencil = checkRenderTarget(L, -1);
|
||||
else if (!lua_isnoneornil(L, -1))
|
||||
targets.depthStencil.canvas = luax_checkcanvas(L, -1);
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -261,19 +271,19 @@ int w_setCanvas(lua_State *L)
|
||||
if (i == 1 && type != TEXTURE_2D)
|
||||
{
|
||||
target.slice = (int) luaL_checknumber(L, 2) - 1;
|
||||
targets.push_back(target);
|
||||
targets.colors.push_back(target);
|
||||
break;
|
||||
}
|
||||
|
||||
if (i > 1 && type != TEXTURE_2D)
|
||||
return luaL_error(L, "This variant of setCanvas only supports 2D texture types.");
|
||||
|
||||
targets.push_back(target);
|
||||
targets.colors.push_back(target);
|
||||
}
|
||||
}
|
||||
|
||||
luax_catchexcept(L, [&]() {
|
||||
if (targets.size() > 0)
|
||||
if (targets.colors.size() > 0)
|
||||
instance()->setCanvas(targets);
|
||||
else
|
||||
instance()->setCanvas();
|
||||
@@ -282,10 +292,31 @@ int w_setCanvas(lua_State *L)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void pushRenderTarget(lua_State *L, const Graphics::RenderTarget &rt)
|
||||
{
|
||||
lua_createtable(L, 1, 1);
|
||||
|
||||
luax_pushtype(L, rt.canvas);
|
||||
lua_rawseti(L, -2, 1);
|
||||
|
||||
TextureType type = rt.canvas->getTextureType();
|
||||
|
||||
if (type == TEXTURE_2D_ARRAY || type == TEXTURE_VOLUME)
|
||||
{
|
||||
lua_pushnumber(L, rt.slice + 1);
|
||||
lua_setfield(L, -2, "layer");
|
||||
}
|
||||
else if (type == TEXTURE_VOLUME)
|
||||
{
|
||||
lua_pushnumber(L, rt.slice + 1);
|
||||
lua_setfield(L, -2, "face");
|
||||
}
|
||||
}
|
||||
|
||||
int w_getCanvas(lua_State *L)
|
||||
{
|
||||
const std::vector<Graphics::RenderTarget> targets = instance()->getCanvas();
|
||||
int ntargets = (int) targets.size();
|
||||
Graphics::RenderTargets targets = instance()->getCanvas();
|
||||
int ntargets = (int) targets.colors.size();
|
||||
|
||||
if (ntargets == 0)
|
||||
{
|
||||
@@ -294,7 +325,7 @@ int w_getCanvas(lua_State *L)
|
||||
}
|
||||
|
||||
bool hasNon2DTextureType = false;
|
||||
for (const auto &rt : targets)
|
||||
for (const auto &rt : targets.colors)
|
||||
{
|
||||
if (rt.canvas->getTextureType() != TEXTURE_2D)
|
||||
{
|
||||
@@ -303,40 +334,27 @@ int w_getCanvas(lua_State *L)
|
||||
}
|
||||
}
|
||||
|
||||
if (hasNon2DTextureType)
|
||||
if (hasNon2DTextureType || targets.depthStencil.canvas != nullptr)
|
||||
{
|
||||
lua_createtable(L, ntargets, 0);
|
||||
|
||||
for (int i = 0; i < ntargets; i++)
|
||||
{
|
||||
const auto &rt = targets[i];
|
||||
|
||||
lua_createtable(L, 1, 1);
|
||||
|
||||
luax_pushtype(L, rt.canvas);
|
||||
lua_rawseti(L, -2, 1);
|
||||
|
||||
TextureType type = rt.canvas->getTextureType();
|
||||
|
||||
if (type == TEXTURE_2D_ARRAY || type == TEXTURE_VOLUME)
|
||||
{
|
||||
lua_pushnumber(L, rt.slice + 1);
|
||||
lua_setfield(L, -2, "layer");
|
||||
}
|
||||
else if (type == TEXTURE_VOLUME)
|
||||
{
|
||||
lua_pushnumber(L, rt.slice + 1);
|
||||
lua_setfield(L, -2, "face");
|
||||
}
|
||||
|
||||
pushRenderTarget(L, targets.colors[i]);
|
||||
lua_rawseti(L, -2, i + 1);
|
||||
}
|
||||
|
||||
if (targets.depthStencil.canvas != nullptr)
|
||||
{
|
||||
pushRenderTarget(L, targets.depthStencil);
|
||||
lua_setfield(L, -2, "depthstencil");
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (const auto &rt : targets)
|
||||
for (const auto &rt : targets.colors)
|
||||
luax_pushtype(L, rt.canvas);
|
||||
|
||||
return ntargets;
|
||||
|
||||
@@ -237,6 +237,13 @@ int w_Texture_getFormat(lua_State *L)
|
||||
return 1;
|
||||
}
|
||||
|
||||
int w_Texture_isReadable(lua_State *L)
|
||||
{
|
||||
Texture *t = luax_checktexture(L, 1);
|
||||
luax_pushboolean(L, t->isReadable());
|
||||
return 1;
|
||||
}
|
||||
|
||||
const luaL_Reg w_Texture_functions[] =
|
||||
{
|
||||
{ "getTextureType", w_Texture_getTextureType },
|
||||
@@ -257,6 +264,7 @@ const luaL_Reg w_Texture_functions[] =
|
||||
{ "setWrap", w_Texture_setWrap },
|
||||
{ "getWrap", w_Texture_getWrap },
|
||||
{ "getFormat", w_Texture_getFormat },
|
||||
{ "isReadable", w_Texture_isReadable },
|
||||
{ 0, 0 }
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user