Add mipmapping support to Canvases.

- Added an optional mipmap index argument to the non-table variant of love.graphics.setCanvas, and an optional ‘mipmap’ field to the table variant.
- Canvas:setMipmapFilter now works.
- Added Canvas:generateMipmaps.
- Added Canvas:getMipmapMode.
- Added a new ‘mipmaps’ enum field to the table passed into love.graphics.newCanvas. Accepted values are “none” (default), “manual”, and “auto”.

If a Canvas has the manual mipmap mode, you will either need to render to a mipmap level or call Canvas:generateMipmaps. If it has the auto mode, mipmaps will be automatically generated after rendering to that Canvas. Generating mipmaps is not free.

--HG--
branch : minor
This commit is contained in:
Alex Szpakowski
2017-04-15 01:58:03 -03:00
parent a0ef2ac28b
commit 7161d600b7
16 changed files with 396 additions and 201 deletions
+66 -2
View File
@@ -29,9 +29,44 @@ namespace graphics
love::Type Canvas::type("Canvas", &Texture::type);
int Canvas::canvasCount = 0;
Canvas::Canvas(TextureType textype)
: Texture(textype)
Canvas::Canvas(const Settings &settings)
: Texture(settings.type)
{
this->settings = settings;
width = settings.width;
height = settings.height;
pixelWidth = (int) ((width * settings.pixeldensity) + 0.5);
pixelHeight = (int) ((height * settings.pixeldensity) + 0.5);
format = settings.format;
if (texType == TEXTURE_VOLUME)
depth = settings.layers;
else if (texType == TEXTURE_2D_ARRAY)
layers = settings.layers;
else
layers = 1;
if (width <= 0 || height <= 0 || layers <= 0)
throw love::Exception("Canvas dimensions must be greater than 0.");
if (texType != TEXTURE_2D && settings.msaa > 1)
throw love::Exception("MSAA is only supported for Canvases with the 2D texture type.");
if (settings.readable.hasValue)
readable = settings.readable.value;
else
readable = !isPixelFormatDepthStencil(format);
if (readable && isPixelFormatDepthStencil(format) && settings.msaa > 1)
throw love::Exception("Readable depth/stencil Canvases with MSAA are not currently supported.");
if ((!readable || settings.msaa > 1) && settings.mipmaps != MIPMAP_NONE)
throw love::Exception("Non-readable and MSAA textures cannot have mipmaps.");
mipmapCount = settings.mipmaps == MIPMAP_NONE ? 1 : getMipmapCount(pixelWidth, pixelHeight);
canvasCount++;
}
@@ -40,6 +75,16 @@ Canvas::~Canvas()
canvasCount--;
}
Canvas::MipmapMode Canvas::getMipmapMode() const
{
return settings.mipmaps;
}
int Canvas::getRequestedMSAA() const
{
return settings.msaa;
}
void Canvas::draw(Graphics *gfx, Quad *q, const Matrix4 &t)
{
if (gfx->isCanvasActive(this))
@@ -56,6 +101,25 @@ void Canvas::drawLayer(Graphics *gfx, int layer, Quad *quad, const Matrix4 &m)
Texture::drawLayer(gfx, layer, quad, m);
}
bool Canvas::getConstant(const char *in, MipmapMode &out)
{
return mipmapModes.find(in, out);
}
bool Canvas::getConstant(MipmapMode in, const char *&out)
{
return mipmapModes.find(in, out);
}
StringMap<Canvas::MipmapMode, Canvas::MIPMAP_MAX_ENUM>::Entry Canvas::mipmapEntries[] =
{
{ "none", MIPMAP_NONE },
{ "manual", MIPMAP_MANUAL },
{ "auto", MIPMAP_AUTO },
};
StringMap<Canvas::MipmapMode, Canvas::MIPMAP_MAX_ENUM> Canvas::mipmapModes(Canvas::mipmapEntries, sizeof(Canvas::mipmapEntries));
} // graphics
} // love
+30 -11
View File
@@ -23,6 +23,8 @@
#include "image/Image.h"
#include "image/ImageData.h"
#include "Texture.h"
#include "common/Optional.h"
#include "common/StringMap.h"
namespace love
{
@@ -37,38 +39,55 @@ public:
static love::Type type;
enum MipmapMode
{
MIPMAP_NONE,
MIPMAP_MANUAL,
MIPMAP_AUTO,
MIPMAP_MAX_ENUM
};
struct Settings
{
// Defaults to true for color pixel formats, and false for depth/stencil.
struct Readable
{
bool set = false;
bool value = false;
};
int width = 1;
int height = 1;
int layers = 1; // depth for 3D textures
MipmapMode mipmaps = MIPMAP_NONE;
PixelFormat format = PIXELFORMAT_NORMAL;
TextureType type = TEXTURE_2D;
float pixeldensity = 1.0f;
int msaa = 0;
Readable readable;
OptionalBool readable;
};
Canvas(TextureType textype);
Canvas(const Settings &settings);
virtual ~Canvas();
virtual love::image::ImageData *newImageData(love::image::Image *module, int slice, int x, int y, int w, int h) = 0;
MipmapMode getMipmapMode() const;
int getRequestedMSAA() const;
virtual love::image::ImageData *newImageData(love::image::Image *module, int slice, int mipmap, const Rect &rect) = 0;
virtual void generateMipmaps() = 0;
virtual int getMSAA() const = 0;
virtual int getRequestedMSAA() 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;
static int canvasCount;
static bool getConstant(const char *in, MipmapMode &out);
static bool getConstant(MipmapMode in, const char *&out);
protected:
Settings settings;
private:
static StringMap<MipmapMode, MIPMAP_MAX_ENUM>::Entry mipmapEntries[];
static StringMap<MipmapMode, MIPMAP_MAX_ENUM> mipmapModes;
}; // Canvas
+7 -6
View File
@@ -301,7 +301,7 @@ void Graphics::restoreStateChecked(const DisplayState &s)
{
const auto &rt1 = sRTs.colors[i];
const auto &rt2 = curRTs.colors[i];
if (rt1.canvas.get() != rt2.canvas.get() || rt1.slice != rt2.slice)
if (rt1.canvas.get() != rt2.canvas.get() || rt1.slice != rt2.slice || rt1.mipmap != rt2.mipmap)
{
canvaseschanged = true;
break;
@@ -309,7 +309,8 @@ void Graphics::restoreStateChecked(const DisplayState &s)
}
if (!canvaseschanged && (sRTs.depthStencil.canvas.get() != curRTs.depthStencil.canvas.get()
|| sRTs.depthStencil.slice != curRTs.depthStencil.slice))
|| sRTs.depthStencil.slice != curRTs.depthStencil.slice
|| sRTs.depthStencil.mipmap != curRTs.depthStencil.mipmap))
{
canvaseschanged = true;
}
@@ -422,9 +423,9 @@ void Graphics::setCanvas(const RenderTargetsStrongRef &rts)
targets.colors.reserve(rts.colors.size());
for (const auto &rt : rts.colors)
targets.colors.emplace_back(rt.canvas.get(), rt.slice);
targets.colors.emplace_back(rt.canvas.get(), rt.slice, rt.mipmap);
targets.depthStencil = RenderTarget(rts.depthStencil.canvas, rts.depthStencil.slice);
targets.depthStencil = RenderTarget(rts.depthStencil.canvas, rts.depthStencil.slice, rts.depthStencil.mipmap);
return setCanvas(targets);
}
@@ -437,9 +438,9 @@ Graphics::RenderTargets Graphics::getCanvas() const
rts.colors.reserve(curRTs.colors.size());
for (const auto &rt : curRTs.colors)
rts.colors.emplace_back(rt.canvas.get(), rt.slice);
rts.colors.emplace_back(rt.canvas.get(), rt.slice, rt.mipmap);
rts.depthStencil = RenderTarget(curRTs.depthStencil.canvas, curRTs.depthStencil.slice);
rts.depthStencil = RenderTarget(curRTs.depthStencil.canvas, curRTs.depthStencil.slice, curRTs.depthStencil.mipmap);
return rts;
}
+6 -2
View File
@@ -292,10 +292,12 @@ public:
{
Canvas *canvas;
int slice;
int mipmap;
RenderTarget(Canvas *canvas, int slice = 0)
RenderTarget(Canvas *canvas, int slice = 0, int mipmap = 0)
: canvas(canvas)
, slice(slice)
, mipmap(mipmap)
{}
RenderTarget()
@@ -308,10 +310,12 @@ public:
{
StrongRef<Canvas> canvas;
int slice = 0;
int mipmap = 0;
RenderTargetStrongRef(Canvas *canvas, int slice = 0)
RenderTargetStrongRef(Canvas *canvas, int slice = 0, int mipmap = 0)
: canvas(canvas)
, slice(slice)
, mipmap(mipmap)
{}
};
+25 -10
View File
@@ -175,19 +175,19 @@ void Texture::drawLayer(Graphics *gfx, int layer, Quad *q, const Matrix4 &m)
}
}
int Texture::getWidth() const
int Texture::getWidth(int mip) const
{
return width;
return std::max(width >> mip, 1);
}
int Texture::getHeight() const
int Texture::getHeight(int mip) const
{
return height;
return std::max(height >> mip, 1);
}
int Texture::getDepth() const
int Texture::getDepth(int mip) const
{
return depth;
return std::max(depth >> mip, 1);
}
int Texture::getLayerCount() const
@@ -200,14 +200,14 @@ int Texture::getMipmapCount() const
return mipmapCount;
}
int Texture::getPixelWidth() const
int Texture::getPixelWidth(int mip) const
{
return pixelWidth;
return std::max(pixelWidth >> mip, 1);
}
int Texture::getPixelHeight() const
int Texture::getPixelHeight(int mip) const
{
return pixelHeight;
return std::max(pixelHeight >> mip, 1);
}
float Texture::getPixelDensity() const
@@ -215,6 +215,21 @@ float Texture::getPixelDensity() const
return (float) pixelHeight / (float) height;
}
void Texture::setFilter(const Filter &f)
{
if (!validateFilter(f, getMipmapCount() > 1))
{
if (f.mipmap != FILTER_NONE && getMipmapCount() == 1)
throw love::Exception("Non-mipmapped texture cannot have mipmap filtering.");
else
throw love::Exception("Invalid texture filter.");
}
Graphics::flushStreamDrawsGlobal();
filter = f;
}
const Texture::Filter &Texture::getFilter() const
{
return filter;
+6 -6
View File
@@ -124,18 +124,18 @@ public:
bool isReadable() const;
int getWidth() const;
int getHeight() const;
int getDepth() const;
int getWidth(int mip = 0) const;
int getHeight(int mip = 0) const;
int getDepth(int mip = 0) const;
int getLayerCount() const;
int getMipmapCount() const;
virtual int getPixelWidth() const;
virtual int getPixelHeight() const;
int getPixelWidth(int mip = 0) const;
int getPixelHeight(int mip = 0) const;
float getPixelDensity() const;
virtual void setFilter(const Filter &f) = 0;
virtual void setFilter(const Filter &f);
virtual const Filter &getFilter() const;
virtual bool setWrap(const Wrap &w) = 0;
+82 -60
View File
@@ -30,7 +30,7 @@ namespace graphics
namespace opengl
{
static GLenum createFBO(GLuint &framebuffer, TextureType texType, PixelFormat format, GLuint texture, int layers)
static GLenum createFBO(GLuint &framebuffer, TextureType texType, PixelFormat format, GLuint texture, int layers, int mips)
{
// get currently bound fbo to reset to it later
GLuint current_fbo = gl.getFramebuffer(OpenGL::FRAMEBUFFER_ALL);
@@ -48,16 +48,23 @@ static GLenum createFBO(GLuint &framebuffer, TextureType texType, PixelFormat fo
// Make sure all faces and layers of the texture are initialized to
// transparent black. This is unfortunately probably pretty slow for
// 2D-array and 3D textures with a lot of layers...
for (int layer = layers - 1; layer >= 0; layer--)
for (int mip = mips - 1; mip >= 0; mip--)
{
for (int face = faces - 1; face >= 0; face--)
{
for (GLenum attachment : fmt.framebufferAttachments)
{
if (attachment == GL_NONE)
continue;
int nlayers = layers;
if (texType == TEXTURE_VOLUME)
nlayers = std::max(layers >> mip, 1);
gl.framebufferTexture(attachment, texType, texture, 0, layer, face);
for (int layer = nlayers - 1; layer >= 0; layer--)
{
for (int face = faces - 1; face >= 0; face--)
{
for (GLenum attachment : fmt.framebufferAttachments)
{
if (attachment == GL_NONE)
continue;
gl.framebufferTexture(attachment, texType, texture, mip, layer, face);
}
if (isPixelFormatDepthStencil(format))
{
@@ -146,41 +153,14 @@ static bool createRenderbuffer(int width, int height, int &samples, PixelFormat
}
Canvas::Canvas(const Settings &settings)
: love::graphics::Canvas(settings.type)
: love::graphics::Canvas(settings)
, fbo(0)
, texture(0)
, renderbuffer(0)
, requestedSamples(settings.msaa)
, actualSamples(0)
, textureMemory(0)
{
width = settings.width;
height = settings.height;
pixelWidth = (int) ((width * settings.pixeldensity) + 0.5);
pixelHeight = (int) ((height * settings.pixeldensity) + 0.5);
if (texType == TEXTURE_VOLUME)
depth = settings.layers;
else if (texType == TEXTURE_2D_ARRAY)
layers = settings.layers;
else
layers = 1;
if (width <= 0 || height <= 0 || layers <= 0)
throw love::Exception("Canvas dimensions must be greater than 0.");
if (texType != TEXTURE_2D && settings.msaa > 1)
throw love::Exception("MSAA is only supported for Canvases with the 2D texture type.");
format = getSizedFormat(settings.format);
if (settings.readable.set)
readable = settings.readable.value;
else
readable = !isPixelFormatDepthStencil(format);
if (readable && isPixelFormatDepthStencil(format) && settings.msaa > 1)
throw love::Exception("Readable depth/stencil Canvases with MSAA are not currently supported.");
format = getSizedFormat(format);
initQuad();
loadVolatile();
@@ -212,7 +192,7 @@ bool Canvas::loadVolatile()
throw love::Exception("The %s%s canvas format is not supported by your OpenGL drivers.", fstr, readablestr);
}
if (requestedSamples > 1 && texType != TEXTURE_2D)
if (getRequestedMSAA() > 1 && texType != TEXTURE_2D)
throw love::Exception("MSAA is only supported for 2D texture types.");
if (!readable && texType != TEXTURE_2D)
@@ -221,7 +201,7 @@ bool Canvas::loadVolatile()
if (!gl.isTextureTypeSupported(texType))
{
const char *textypestr = "unknown";
getConstant(texType, textypestr);
Texture::getConstant(texType, textypestr);
throw love::Exception("%s textures are not supported on this system!", textypestr);
}
@@ -277,13 +257,14 @@ bool Canvas::loadVolatile()
setFilter(filter);
setWrap(wrap);
setMipmapSharpness(mipmapSharpness);
setDepthSampleMode(depthCompareMode);
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))
if (!gl.rawTexStorage(texType, mipmapCount, format, isSRGB, pixelWidth, pixelHeight, texType == TEXTURE_VOLUME ? depth : layers))
{
status = GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT;
return false;
@@ -298,7 +279,7 @@ bool Canvas::loadVolatile()
}
// Create a canvas-local FBO used for glReadPixels as well as MSAA blitting.
status = createFBO(fbo, texType, format, texture, texType == TEXTURE_VOLUME ? depth : layers);
status = createFBO(fbo, texType, format, texture, texType == TEXTURE_VOLUME ? depth : layers, mipmapCount);
if (status != GL_FRAMEBUFFER_COMPLETE)
{
@@ -313,7 +294,7 @@ bool Canvas::loadVolatile()
// getMaxRenderbufferSamples will be 0 on systems that don't support
// multisampled renderbuffers / don't export FBO multisample extensions.
actualSamples = requestedSamples;
actualSamples = getRequestedMSAA();
actualSamples = std::min(actualSamples, gl.getMaxRenderbufferSamples());
actualSamples = std::max(actualSamples, 0);
actualSamples = actualSamples == 1 ? 0 : actualSamples;
@@ -330,6 +311,9 @@ bool Canvas::loadVolatile()
else if (actualSamples > 0)
textureMemory *= actualSamples;
if (getMipmapCount() > 1)
textureMemory *= 1.33334;
gl.updateTextureMemorySize(prevmemsize, textureMemory);
return true;
@@ -356,12 +340,16 @@ void Canvas::unloadVolatile()
void Canvas::setFilter(const Texture::Filter &f)
{
if (!validateFilter(f, false))
throw love::Exception("Invalid texture filter.");
Texture::setFilter(f);
Graphics::flushStreamDrawsGlobal();
if (!OpenGL::hasTextureFilteringSupport(getPixelFormat()))
{
filter.mag = filter.min = FILTER_NEAREST;
if (filter.mipmap == FILTER_LINEAR)
filter.mipmap = FILTER_NEAREST;
}
filter = f;
gl.bindTextureToUnit(this, 0, false);
gl.setTextureFilter(texType, filter);
}
@@ -402,9 +390,25 @@ bool Canvas::setWrap(const Texture::Wrap &w)
return success;
}
bool Canvas::setMipmapSharpness(float /*sharpness*/)
bool Canvas::setMipmapSharpness(float sharpness)
{
return false;
if (!gl.isSamplerLODBiasSupported())
return false;
Graphics::flushStreamDrawsGlobal();
float maxbias = gl.getMaxLODBias();
if (maxbias > 0.01f)
maxbias -= 0.0f;
mipmapSharpness = std::min(std::max(sharpness, -maxbias), maxbias);
gl.bindTextureToUnit(this, 0, false);
// negative bias is sharper
glTexParameterf(gl.getGLTextureType(texType), GL_TEXTURE_LOD_BIAS, -mipmapSharpness);
return true;
}
void Canvas::setDepthSampleMode(Optional<CompareMode> mode)
@@ -413,11 +417,11 @@ void Canvas::setDepthSampleMode(Optional<CompareMode> mode)
bool supported = gl.isDepthCompareSampleSupported();
if (mode.hasValue && !supported)
throw love::Exception("Depth comparison sampling in shaders is not supported on this system.");
if (mode.hasValue)
{
if (!supported)
throw love::Exception("Depth comparison sampling in shaders is not supported on this system.");
Graphics::flushStreamDrawsGlobal();
gl.bindTextureToUnit(texType, texture, 0, false);
@@ -448,15 +452,18 @@ ptrdiff_t Canvas::getHandle() const
return texture;
}
love::image::ImageData *Canvas::newImageData(love::image::Image *module, int slice, int x, int y, int w, int h)
love::image::ImageData *Canvas::newImageData(love::image::Image *module, int slice, int mipmap, const Rect &r)
{
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())
if (isPixelFormatDepthStencil(getPixelFormat()))
throw love::Exception("Canvas:newImageData cannot be called on Canvases with depth/stencil pixel formats.");
if (r.x < 0 || r.y < 0 || r.w <= 0 || r.h <= 0 || (r.x + r.w) > getPixelWidth(mipmap) || (r.y + r.h) > getPixelHeight(mipmap))
throw love::Exception("Invalid rectangle dimensions.");
if (slice < 0 || (texType == TEXTURE_VOLUME && slice >= depth)
if (slice < 0 || (texType == TEXTURE_VOLUME && slice >= getDepth(mipmap))
|| (texType == TEXTURE_2D_ARRAY && slice >= layers)
|| (texType == TEXTURE_CUBE && slice >= 6))
{
@@ -489,7 +496,7 @@ love::image::ImageData *Canvas::newImageData(love::image::Image *module, int sli
break;
}
love::image::ImageData *imagedata = module->newImageData(w, h, dataformat);
love::image::ImageData *imagedata = module->newImageData(r.w, r.h, dataformat);
bool isSRGB = false;
OpenGL::TextureFormat fmt = gl.convertPixelFormat(dataformat, false, isSRGB);
@@ -497,16 +504,16 @@ love::image::ImageData *Canvas::newImageData(love::image::Image *module, int sli
GLuint current_fbo = gl.getFramebuffer(OpenGL::FRAMEBUFFER_ALL);
gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, getFBO());
if (slice > 0)
if (slice > 0 || mipmap > 0)
{
int layer = texType == TEXTURE_CUBE ? 0 : slice;
int face = texType == TEXTURE_CUBE ? slice : 0;
gl.framebufferTexture(GL_COLOR_ATTACHMENT0, texType, texture, 0, layer, face);
gl.framebufferTexture(GL_COLOR_ATTACHMENT0, texType, texture, mipmap, layer, face);
}
glReadPixels(x, y, w, h, fmt.externalformat, fmt.type, imagedata->getData());
glReadPixels(r.x, r.y, r.w, r.h, fmt.externalformat, fmt.type, imagedata->getData());
if (slice > 0)
if (slice > 0 || mipmap > 0)
gl.framebufferTexture(GL_COLOR_ATTACHMENT0, texType, texture, 0, 0, 0);
gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, current_fbo);
@@ -514,6 +521,21 @@ love::image::ImageData *Canvas::newImageData(love::image::Image *module, int sli
return imagedata;
}
void Canvas::generateMipmaps()
{
if (getMipmapCount() == 1 || getMipmapMode() == MIPMAP_NONE)
throw love::Exception("generateMipmaps can only be called on a Canvas which was created with mipmaps enabled.");
gl.bindTextureToUnit(this, 0, false);
GLenum gltextype = OpenGL::getGLTextureType(texType);
if (gl.bugs.generateMipmapsRequiresTexture2DEnable)
glEnable(gltextype);
glGenerateMipmap(gltextype);
}
PixelFormat Canvas::getSizedFormat(PixelFormat format)
{
switch (format)
+2 -7
View File
@@ -53,18 +53,14 @@ public:
void setDepthSampleMode(Optional<CompareMode> mode) override;
ptrdiff_t getHandle() const override;
love::image::ImageData *newImageData(love::image::Image *module, int slice, int x, int y, int w, int h) override;
love::image::ImageData *newImageData(love::image::Image *module, int slice, int mipmap, const Rect &rect) override;
void generateMipmaps() override;
int getMSAA() const override
{
return actualSamples;
}
int getRequestedMSAA() const override
{
return requestedSamples;
}
ptrdiff_t getRenderTargetHandle() const override
{
return renderbuffer != 0 ? renderbuffer : texture;
@@ -114,7 +110,6 @@ private:
GLenum status;
int requestedSamples;
int actualSamples;
size_t textureMemory;
+42 -15
View File
@@ -518,15 +518,17 @@ void Graphics::setCanvas(const RenderTargets &rts)
for (int i = 0; i < ncanvases; i++)
{
if (rts.colors[i].canvas != prevRTs.colors[i].canvas.get()
|| rts.colors[i].slice != prevRTs.colors[i].slice)
|| rts.colors[i].slice != prevRTs.colors[i].slice
|| rts.colors[i].mipmap != prevRTs.colors[i].mipmap)
{
modified = true;
break;
}
}
if (!modified && rts.depthStencil.canvas == prevRTs.depthStencil.canvas
&& rts.depthStencil.slice == prevRTs.depthStencil.slice)
if (!modified && (rts.depthStencil.canvas != prevRTs.depthStencil.canvas
|| rts.depthStencil.slice != prevRTs.depthStencil.slice
|| rts.depthStencil.mipmap != prevRTs.depthStencil.mipmap))
{
modified = true;
}
@@ -546,16 +548,23 @@ void Graphics::setCanvas(const RenderTargets &rts)
if (isPixelFormatDepthStencil(firstformat))
throw love::Exception("Depth/stencil format Canvases must be used with the 'depthstencil' field of the table passed into setCanvas.");
if (rts.colors[0].mipmap < 0 || rts.colors[0].mipmap >= firstcanvas->getMipmapCount())
throw love::Exception("Invalid mipmap level %d.", rts.colors[0].mipmap + 1);
bool hasSRGBcanvas = firstformat == PIXELFORMAT_sRGBA8;
int pixelwidth = firstcanvas->getPixelWidth();
int pixelheight = firstcanvas->getPixelHeight();
int pixelwidth = firstcanvas->getPixelWidth(rts.colors[0].mipmap);
int pixelheight = firstcanvas->getPixelHeight(rts.colors[0].mipmap);
for (int i = 1; i < ncanvases; i++)
{
love::graphics::Canvas *c = rts.colors[i].canvas;
PixelFormat format = c->getPixelFormat();
int mip = rts.colors[i].mipmap;
if (c->getPixelWidth() != pixelwidth || c->getPixelHeight() != pixelheight)
if (mip < 0 || mip >= c->getMipmapCount())
throw love::Exception("Invalid mipmap level %d.", mip + 1);
if (c->getPixelWidth(mip) != pixelwidth || c->getPixelHeight(mip) != pixelheight)
throw love::Exception("All canvases must have the same pixel dimensions.");
if (!multiformatsupported && format != firstformat)
@@ -574,15 +583,19 @@ void Graphics::setCanvas(const RenderTargets &rts)
if (rts.depthStencil.canvas != nullptr)
{
love::graphics::Canvas *c = rts.depthStencil.canvas;
int mip = rts.depthStencil.mipmap;
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)
if (c->getPixelWidth(mip) != pixelwidth || c->getPixelHeight(mip) != 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.");
if (mip < 0 || mip >= c->getMipmapCount())
throw love::Exception("Invalid mipmap level %d.", mip + 1);
}
OpenGL::TempDebugGroup debuggroup("setCanvas(...)");
@@ -598,8 +611,8 @@ void Graphics::setCanvas(const RenderTargets &rts)
if (state.scissor)
setScissor(state.scissorRect);
int w = firstcanvas->getWidth();
int h = firstcanvas->getHeight();
int w = firstcanvas->getWidth(rts.colors[0].mipmap);
int h = firstcanvas->getHeight(rts.colors[0].mipmap);
projectionMatrix = Matrix4::ortho(0.0, (float) w, 0.0, (float) h);
// Make sure the correct sRGB setting is used when drawing to the canvases.
@@ -666,17 +679,19 @@ void Graphics::endPass()
flushStreamDraws();
auto &rts = states.back().renderTargets;
love::graphics::Canvas *depthstencil = rts.depthStencil.canvas.get();
// Discard the stencil buffer if we're using an internal cached one.
if (rts.depthStencil.canvas.get() == nullptr)
if (depthstencil == 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 (rts.colors.size() > 0 && rts.colors[0].canvas->getMSAA() > 1)
{
int w = rts.colors[0].canvas->getPixelWidth();
int h = rts.colors[0].canvas->getPixelHeight();
int mip = rts.colors[0].mipmap;
int w = rts.colors[0].canvas->getPixelWidth(mip);
int h = rts.colors[0].canvas->getPixelHeight(mip);
for (int i = 0; i < (int) rts.colors.size(); i++)
{
@@ -695,6 +710,16 @@ void Graphics::endPass()
glBlitFramebuffer(0, 0, w, h, 0, 0, w, h, GL_COLOR_BUFFER_BIT, GL_NEAREST);
}
}
for (const auto &rt : rts.colors)
{
if (rt.canvas->getMipmapMode() == Canvas::MIPMAP_AUTO && rt.mipmap == 0)
rt.canvas->generateMipmaps();
}
int dsmipmap = rts.depthStencil.mipmap;
if (depthstencil != nullptr && depthstencil->getMipmapMode() == Canvas::MIPMAP_AUTO && dsmipmap == 0)
depthstencil->generateMipmaps();
}
void Graphics::clear(OptionalColorf c, OptionalInt stencil, OptionalDouble depth)
@@ -892,8 +917,9 @@ void Graphics::bindCachedFBO(const RenderTargets &targets)
}
else
{
int w = targets.colors[0].canvas->getPixelWidth();
int h = targets.colors[0].canvas->getPixelHeight();
int mip = targets.colors[0].mipmap;
int w = targets.colors[0].canvas->getPixelWidth(mip);
int h = targets.colors[0].canvas->getPixelHeight(mip);
int msaa = targets.colors[0].canvas->getMSAA();
int reqmsaa = targets.colors[0].canvas->getRequestedMSAA();
@@ -938,8 +964,9 @@ void Graphics::bindCachedFBO(const RenderTargets &targets)
int layer = textype == TEXTURE_CUBE ? 0 : rt.slice;
int face = textype == TEXTURE_CUBE ? rt.slice : 0;
int level = rt.mipmap;
gl.framebufferTexture(attachment, textype, handle, 0, layer, face);
gl.framebufferTexture(attachment, textype, handle, level, layer, face);
}
}
};
+9 -24
View File
@@ -33,8 +33,6 @@ namespace graphics
namespace opengl
{
float Image::maxMipmapSharpness = 0.0f;
Image::Image(TextureType textype, PixelFormat format, int width, int height, int slices, const Settings &settings)
: love::graphics::Image(Slices(textype), settings, false)
, texture(0)
@@ -98,8 +96,6 @@ void Image::init(PixelFormat fmt, int w, int h, const Settings &settings)
void Image::generateMipmaps()
{
// The GL_GENERATE_MIPMAP texparameter is set in loadVolatile if we don't
// have support for glGenerateMipmap.
if (getMipmapCount() > 1 && !isCompressed() &&
(GLAD_ES_VERSION_2_0 || GLAD_VERSION_3_0 || GLAD_ARB_framebuffer_object || GLAD_EXT_framebuffer_object))
{
@@ -270,9 +266,6 @@ bool Image::loadVolatile()
filter.mipmap = FILTER_NONE;
}
if (maxMipmapSharpness == 0.0f && GLAD_VERSION_1_4)
glGetFloatv(GL_MAX_TEXTURE_LOD_BIAS, &maxMipmapSharpness);
glGenTextures(1, &texture);
gl.bindTextureToUnit(this, 0, false);
@@ -418,17 +411,7 @@ ptrdiff_t Image::getHandle() const
void Image::setFilter(const Texture::Filter &f)
{
if (!validateFilter(f, getMipmapCount() > 1))
{
if (f.mipmap != FILTER_NONE && getMipmapCount() == 1)
throw love::Exception("Non-mipmapped image cannot have mipmap filtering.");
else
throw love::Exception("Invalid texture filter.");
}
Graphics::flushStreamDrawsGlobal();
filter = f;
Texture::setFilter(f);
if (!OpenGL::hasTextureFilteringSupport(getPixelFormat()))
{
@@ -487,20 +470,22 @@ bool Image::setWrap(const Texture::Wrap &w)
bool Image::setMipmapSharpness(float sharpness)
{
// OpenGL ES doesn't support LOD bias via glTexParameter.
if (!GLAD_VERSION_1_4)
if (!gl.isSamplerLODBiasSupported())
return false;
Graphics::flushStreamDrawsGlobal();
// LOD bias has the range (-maxbias, maxbias)
mipmapSharpness = std::min(std::max(sharpness, -maxMipmapSharpness + 0.01f), maxMipmapSharpness - 0.01f);
float maxbias = gl.getMaxLODBias();
if (maxbias > 0.01f)
maxbias -= 0.0f;
mipmapSharpness = std::min(std::max(sharpness, -maxbias), maxbias);
gl.bindTextureToUnit(this, 0, false);
// negative bias is sharper
GLenum gltextype = OpenGL::getGLTextureType(texType);
glTexParameterf(gltextype, GL_TEXTURE_LOD_BIAS, -mipmapSharpness);
glTexParameterf(gl.getGLTextureType(texType), GL_TEXTURE_LOD_BIAS, -mipmapSharpness);
return true;
}
-2
View File
@@ -86,8 +86,6 @@ private:
size_t textureMemorySize;
static float maxMipmapSharpness;
}; // Image
} // opengl
+23 -21
View File
@@ -424,6 +424,11 @@ void OpenGL::initMaxValues()
else
glGetFloatv(GL_ALIASED_POINT_SIZE_RANGE, limits);
maxPointSize = limits[1];
if (isSamplerLODBiasSupported())
glGetFloatv(GL_MAX_TEXTURE_LOD_BIAS, &maxLODBias);
else
maxLODBias = 0.0f;
}
void OpenGL::createDefaultTexture()
@@ -922,16 +927,10 @@ void OpenGL::deleteTexture(GLuint texture)
void OpenGL::setTextureFilter(TextureType target, graphics::Texture::Filter &f)
{
GLint gmin, gmag;
GLint gmin = f.min == Texture::FILTER_NEAREST ? GL_NEAREST : GL_LINEAR;
GLint gmag = f.mag == Texture::FILTER_NEAREST ? GL_NEAREST : GL_LINEAR;
if (f.mipmap == Texture::FILTER_NONE)
{
if (f.min == Texture::FILTER_NEAREST)
gmin = GL_NEAREST;
else // f.min == Texture::FILTER_LINEAR
gmin = GL_LINEAR;
}
else
if (f.mipmap != Texture::FILTER_NONE)
{
if (f.min == Texture::FILTER_NEAREST && f.mipmap == Texture::FILTER_NEAREST)
gmin = GL_NEAREST_MIPMAP_NEAREST;
@@ -945,17 +944,6 @@ void OpenGL::setTextureFilter(TextureType target, graphics::Texture::Filter &f)
gmin = GL_LINEAR;
}
switch (f.mag)
{
case Texture::FILTER_NEAREST:
gmag = GL_NEAREST;
break;
case Texture::FILTER_LINEAR:
default:
gmag = GL_LINEAR;
break;
}
GLenum gltarget = getGLTextureType(target);
glTexParameteri(gltarget, GL_TEXTURE_MIN_FILTER, gmin);
@@ -1138,6 +1126,11 @@ bool OpenGL::isDepthCompareSampleSupported() const
return GLAD_VERSION_2_0 || GLAD_ES_VERSION_3_0 || GLAD_EXT_shadow_samplers;
}
bool OpenGL::isSamplerLODBiasSupported() const
{
return GLAD_VERSION_1_4;
}
int OpenGL::getMax2DTextureSize() const
{
return std::max(max2DTextureSize, 1);
@@ -1183,6 +1176,11 @@ float OpenGL::getMaxAnisotropy() const
return maxAnisotropy;
}
float OpenGL::getMaxLODBias() const
{
return maxLODBias;
}
void OpenGL::updateTextureMemorySize(size_t oldsize, size_t newsize)
{
int64 memsize = (int64) stats.textureMemory + ((int64) newsize - (int64) oldsize);
@@ -1748,10 +1746,14 @@ bool OpenGL::hasTextureFilteringSupport(PixelFormat pixelformat)
{
switch (pixelformat)
{
case PIXELFORMAT_R16F:
case PIXELFORMAT_RG16F:
case PIXELFORMAT_RGBA16F:
return GLAD_VERSION_1_1 || GLAD_ES_VERSION_3_0 || GLAD_OES_texture_half_float_linear;
case PIXELFORMAT_R32F:
case PIXELFORMAT_RG32F:
case PIXELFORMAT_RGBA32F:
return GLAD_VERSION_1_1;
return GLAD_VERSION_1_1 || GLAD_OES_texture_float_linear;
default:
return true;
}
+4
View File
@@ -328,6 +328,7 @@ public:
bool isPixelShaderHighpSupported() const;
bool isInstancingSupported() const;
bool isDepthCompareSampleSupported() const;
bool isSamplerLODBiasSupported() const;
/**
* Returns the maximum supported width or height of a texture.
@@ -363,6 +364,8 @@ public:
**/
float getMaxAnisotropy() const;
float getMaxLODBias() const;
void updateTextureMemorySize(size_t oldsize, size_t newsize);
@@ -407,6 +410,7 @@ private:
bool pixelShaderHighpSupported;
float maxAnisotropy;
float maxLODBias;
int max2DTextureSize;
int max3DTextureSize;
int maxCubeTextureSize;
+34 -16
View File
@@ -91,40 +91,58 @@ int w_Canvas_newImageData(lua_State *L)
love::image::Image *image = luax_getmodule<love::image::Image>(L, love::image::Image::type);
int slice = 0;
int x = 0;
int y = 0;
int w = canvas->getPixelWidth();
int h = canvas->getPixelHeight();
int mipmap = 0;
int startidx = 2;
Rect rect;
rect.x = 0;
rect.y = 0;
rect.w = canvas->getPixelWidth();
rect.h = canvas->getPixelHeight();
if (canvas->getTextureType() != TEXTURE_2D)
if (!lua_isnoneornil(L, 2))
{
slice = (int) luaL_checknumber(L, startidx);
startidx++;
}
rect.x = (int) luaL_checkinteger(L, 2);
rect.y = (int) luaL_checkinteger(L, 3);
rect.w = (int) luaL_checkinteger(L, 4);
rect.h = (int) luaL_checkinteger(L, 5);
if (!lua_isnoneornil(L, startidx))
{
x = (int) luaL_checknumber(L, startidx + 0);
y = (int) luaL_checknumber(L, startidx + 1);
w = (int) luaL_checknumber(L, startidx + 2);
h = (int) luaL_checknumber(L, startidx + 3);
slice = (int) luaL_optinteger(L, 6, 1) - 1;
mipmap = (int) luaL_optinteger(L, 7, 1) - 1;
}
love::image::ImageData *img = nullptr;
luax_catchexcept(L, [&](){ img = canvas->newImageData(image, slice, x, y, w, h); });
luax_catchexcept(L, [&](){ img = canvas->newImageData(image, slice, mipmap, rect); });
luax_pushtype(L, img);
img->release();
return 1;
}
int w_Canvas_generateMipmaps(lua_State *L)
{
Canvas *c = luax_checkcanvas(L, 1);
luax_catchexcept(L, [&]() { c->generateMipmaps(); });
return 0;
}
int w_Canvas_getMipmapMode(lua_State *L)
{
Canvas *c = luax_checkcanvas(L, 1);
const char *str;
if (!Canvas::getConstant(c->getMipmapMode(), str))
return luaL_error(L, "Unknown mipmap mode.");
lua_pushstring(L, str);
return 1;
}
static const luaL_Reg w_Canvas_functions[] =
{
{ "getMSAA", w_Canvas_getMSAA },
{ "renderTo", w_Canvas_renderTo },
{ "newImageData", w_Canvas_newImageData },
{ "generateMipmaps", w_Canvas_generateMipmaps },
{ "getMipmapMode", w_Canvas_getMipmapMode },
{ 0, 0 }
};
+34 -10
View File
@@ -251,6 +251,8 @@ static Graphics::RenderTarget checkRenderTarget(lua_State *L, int idx)
else if (type == TEXTURE_CUBE)
target.slice = luax_checkintflag(L, idx, "face") - 1;
target.mipmap = luax_intflag(L, idx, "mipmap", 1) - 1;
return target;
}
@@ -308,10 +310,16 @@ int w_setCanvas(lua_State *L)
if (i == 1 && type != TEXTURE_2D)
{
target.slice = (int) luaL_checknumber(L, 2) - 1;
target.slice = (int) luaL_checknumber(L, i + 1) - 1;
target.mipmap = (int) luaL_optinteger(L, i + 2, 1) - 1;
targets.colors.push_back(target);
break;
}
else if (type == TEXTURE_2D && lua_isnumber(L, i + 1))
{
target.mipmap = (int) luaL_optinteger(L, i + 1, 1) - 1;
i++;
}
if (i > 1 && type != TEXTURE_2D)
return luaL_error(L, "This variant of setCanvas only supports 2D texture types.");
@@ -332,7 +340,7 @@ int w_setCanvas(lua_State *L)
static void pushRenderTarget(lua_State *L, const Graphics::RenderTarget &rt)
{
lua_createtable(L, 1, 1);
lua_createtable(L, 1, 2);
luax_pushtype(L, rt.canvas);
lua_rawseti(L, -2, 1);
@@ -349,6 +357,9 @@ static void pushRenderTarget(lua_State *L, const Graphics::RenderTarget &rt)
lua_pushnumber(L, rt.slice + 1);
lua_setfield(L, -2, "face");
}
lua_pushnumber(L, rt.mipmap + 1);
lua_setfield(L, -2, "mipmap");
}
int w_getCanvas(lua_State *L)
@@ -362,17 +373,21 @@ int w_getCanvas(lua_State *L)
return 1;
}
bool hasNon2DTextureType = false;
for (const auto &rt : targets.colors)
bool shouldUseTablesVariant = targets.depthStencil.canvas != nullptr;
if (!shouldUseTablesVariant)
{
if (rt.canvas->getTextureType() != TEXTURE_2D)
for (const auto &rt : targets.colors)
{
hasNon2DTextureType = true;
break;
if (rt.mipmap != 0 || rt.canvas->getTextureType() != TEXTURE_2D)
{
shouldUseTablesVariant = true;
break;
}
}
}
if (hasNon2DTextureType || targets.depthStencil.canvas != nullptr)
if (shouldUseTablesVariant)
{
lua_createtable(L, ntargets, 0);
@@ -1063,7 +1078,7 @@ int w_newCanvas(lua_State *L)
if (!lua_isnoneornil(L, startidx))
{
settings.pixeldensity = (float) luax_numberflag(L, startidx, "pixeldensity", settings.pixeldensity);
settings.msaa = luax_intflag(L, startidx, "msaa", 0);
settings.msaa = luax_intflag(L, startidx, "msaa", settings.msaa);
lua_getfield(L, startidx, "format");
if (!lua_isnoneornil(L, -1))
@@ -1087,10 +1102,19 @@ int w_newCanvas(lua_State *L)
if (!lua_isnoneornil(L, -1))
{
luaL_checktype(L, -1, LUA_TBOOLEAN);
settings.readable.set = true;
settings.readable.hasValue = true;
settings.readable.value = luax_toboolean(L, -1);
}
lua_pop(L, 1);
lua_getfield(L, startidx, "mipmaps");
if (!lua_isnoneornil(L, -1))
{
const char *str = luaL_checkstring(L, -1);
if (!Canvas::getConstant(str, settings.mipmaps))
return luaL_error(L, "Invalid Canvas mipmap mode: %s", str);
}
lua_pop(L, 1);
}
Canvas *canvas = nullptr;
+26 -9
View File
@@ -40,32 +40,48 @@ int w_Texture_getTextureType(lua_State *L)
return 1;
}
static int w__optMipmap(lua_State *L, Texture *t, int idx)
{
int mipmap = 0;
if (!lua_isnoneornil(L, idx))
{
mipmap = (int) luaL_checkinteger(L, idx) - 1;
if (mipmap < 0 || mipmap >= t->getMipmapCount())
luaL_error(L, "Invalid mipmap index: %d", mipmap + 1);
}
return mipmap;
}
int w_Texture_getWidth(lua_State *L)
{
Texture *t = luax_checktexture(L, 1);
lua_pushnumber(L, t->getWidth());
lua_pushnumber(L, t->getWidth(w__optMipmap(L, t, 2)));
return 1;
}
int w_Texture_getHeight(lua_State *L)
{
Texture *t = luax_checktexture(L, 1);
lua_pushnumber(L, t->getHeight());
lua_pushnumber(L, t->getHeight(w__optMipmap(L, t, 2)));
return 1;
}
int w_Texture_getDimensions(lua_State *L)
{
Texture *t = luax_checktexture(L, 1);
lua_pushnumber(L, t->getWidth());
lua_pushnumber(L, t->getHeight());
int mipmap = w__optMipmap(L, t, 2);
lua_pushnumber(L, t->getWidth(mipmap));
lua_pushnumber(L, t->getHeight(mipmap));
return 2;
}
int w_Texture_getDepth(lua_State *L)
{
Texture *t = luax_checktexture(L, 1);
lua_pushnumber(L, t->getDepth());
lua_pushnumber(L, t->getDepth(w__optMipmap(L, t, 2)));
return 1;
}
@@ -86,22 +102,23 @@ int w_Texture_getMipmapCount(lua_State *L)
int w_Texture_getPixelWidth(lua_State *L)
{
Texture *t = luax_checktexture(L, 1);
lua_pushnumber(L, t->getPixelWidth());
lua_pushnumber(L, t->getPixelWidth(w__optMipmap(L, t, 2)));
return 1;
}
int w_Texture_getPixelHeight(lua_State *L)
{
Texture *t = luax_checktexture(L, 1);
lua_pushnumber(L, t->getPixelHeight());
lua_pushnumber(L, t->getPixelHeight(w__optMipmap(L, t, 2)));
return 1;
}
int w_Texture_getPixelDimensions(lua_State *L)
{
Texture *t = luax_checktexture(L, 1);
lua_pushnumber(L, t->getPixelWidth());
lua_pushnumber(L, t->getPixelHeight());
int mipmap = w__optMipmap(L, t, 2);
lua_pushnumber(L, t->getPixelWidth(mipmap));
lua_pushnumber(L, t->getPixelHeight(mipmap));
return 2;
}