Implement Array, Cubemap, and Volume texture types (issue #1111).

- Add love.graphics.newArrayImage, newCubeImage, and newVolumeImage.
- Add love.graphics.newCanvas(w, h, layers) and newCanvas(w, h, layers, settings). Add ‘type’ field to the settings table of newCanvas.

- Add new love.graphics.setCanvas variants: setCanvas(canvas, slice), and setCanvas(canvastable) where canvastable is in the format: {{canvas1, layer=2}, {canvas2, face=5}}

- Add Texture:getTextureType, getDepth, getLayerCount, getMipmapCount, and getFormat.

- Remove Image:getData and Image:refresh.
- Add Image:replacePixels(imagedata [, slice] [, mipmap]).

- Update Canvas:newImageData to accept a slice argument.

- Add love.image.newCubeFaces(imagedata).

--HG--
branch : minor
This commit is contained in:
Alex Szpakowski
2017-02-20 14:58:13 -04:00
parent d70fe5cb89
commit 73fd45558b
91 changed files with 3160 additions and 2003 deletions
+2 -1
View File
@@ -29,7 +29,8 @@ namespace graphics
love::Type Canvas::type("Canvas", &Texture::type);
int Canvas::canvasCount = 0;
Canvas::Canvas()
Canvas::Canvas(TextureType textype)
: Texture(textype)
{
canvasCount++;
}
+6 -2
View File
@@ -39,15 +39,19 @@ public:
struct Settings
{
int width = 1;
int height = 1;
int layers = 1; // depth for 3D textures
PixelFormat format = PIXELFORMAT_NORMAL;
TextureType type = TEXTURE_2D;
float pixeldensity = 1.0f;
int msaa = 0;
};
Canvas();
Canvas(TextureType textype);
virtual ~Canvas();
virtual love::image::ImageData *newImageData(love::image::Image *module, int x, int y, int w, int h) = 0;
virtual love::image::ImageData *newImageData(love::image::Image *module, int slice, int x, int y, int w, int h) = 0;
virtual int getMSAA() const = 0;
virtual int getRequestedMSAA() const = 0;
+81 -2
View File
@@ -81,6 +81,7 @@ Font::Font(love::font::Rasterizer *r, const Texture::Filter &f)
if (!r->hasGlyph(9)) // No tab character in the Rasterizer.
useSpacesAsTab = true;
loadVolatile();
++fontCount;
}
@@ -113,6 +114,72 @@ Font::TextureSize Font::getNextTextureSize() const
return size;
}
bool Font::loadVolatile()
{
textureCacheID++;
createTexture();
return true;
}
void Font::createTexture()
{
auto gfx = Module::getInstance<graphics::Graphics>(Module::M_GRAPHICS);
gfx->flushStreamDraws();
Image *image = nullptr;
TextureSize size = {textureWidth, textureHeight};
TextureSize nextsize = getNextTextureSize();
bool recreatetexture = false;
// If we have an existing texture already, we'll try replacing it with a
// larger-sized one rather than creating a second one. Having a single
// texture reduces texture switches and draw calls when rendering.
if ((nextsize.width > size.width || nextsize.height > size.height) && !images.empty())
{
recreatetexture = true;
size = nextsize;
images.pop_back();
}
Image::Settings settings;
image = gfx->newImage(TEXTURE_2D, pixelFormat, size.width, size.height, 1, settings);
image->setFilter(filter);
// Initialize the texture with transparent black.
size_t bpp = getPixelFormatSize(pixelFormat);
std::vector<uint8> emptydata(size.width * size.height * bpp, 0);
Rect rect = {0, 0, size.width, size.height};
image->replacePixels(emptydata.data(), emptydata.size(), rect, 0, 0, false);
images.emplace_back(image, Acquire::NORETAIN);
textureWidth = size.width;
textureHeight = size.height;
rowHeight = textureX = textureY = TEXTURE_PADDING;
// Re-add the old glyphs if we re-created the existing texture object.
if (recreatetexture)
{
textureCacheID++;
std::vector<uint32> glyphstoadd;
for (const auto &glyphpair : glyphs)
glyphstoadd.push_back(glyphpair.first);
glyphs.clear();
for (uint32 g : glyphstoadd)
addGlyph(g);
}
}
void Font::unloadVolatile()
{
}
love::font::GlyphData *Font::getRasterizerGlyphData(uint32 glyph)
{
// Use spaces for the tab 'glyph'.
@@ -178,7 +245,11 @@ const Font::Glyph &Font::addGlyph(uint32 glyph)
// Don't waste space for empty glyphs.
if (w > 0 && h > 0)
{
uploadGlyphToTexture(gd, g);
Image *image = images.back();
g.texture = image;
Rect rect = {textureX, textureY, gd->getWidth(), gd->getHeight()};
image->replacePixels(gd->getData(), gd->getSize(), rect, 0, 0, false);
double tX = (double) textureX, tY = (double) textureY;
double tWidth = (double) textureWidth, tHeight = (double) textureHeight;
@@ -535,7 +606,7 @@ void Font::printv(graphics::Graphics *gfx, const Matrix4 &t, const std::vector<D
req.formats[0] = vertex::CommonFormat::XYf_STus_RGBAub;
req.indexMode = vertex::TriangleIndexMode::QUADS;
req.vertexCount = cmd.vertexcount;
req.textureHandle = cmd.texture;
req.texture = cmd.texture;
Graphics::StreamVertexData data = gfx->requestStreamDraw(req);
GlyphVertex *vertexdata = (GlyphVertex *) data.stream[0];
@@ -809,6 +880,14 @@ float Font::getLineHeight() const
return lineHeight;
}
void Font::setFilter(const Texture::Filter &f)
{
for (const auto &image : images)
image->setFilter(f);
filter = f;
}
const Texture::Filter &Font::getFilter() const
{
return filter;
+18 -12
View File
@@ -33,8 +33,9 @@
#include "common/Vector.h"
#include "font/Rasterizer.h"
#include "Texture.h"
#include "Image.h"
#include "vertex.h"
#include "Volatile.h"
namespace love
{
@@ -43,7 +44,7 @@ namespace graphics
class Graphics;
class Font : public Object
class Font : public Object, public Volatile
{
public:
@@ -88,7 +89,7 @@ public:
// Used to determine when to change textures in the generated vertex array.
struct DrawCommand
{
ptrdiff_t texture;
Texture *texture;
int startvertex;
int vertexcount;
};
@@ -155,7 +156,7 @@ public:
**/
float getLineHeight() const;
virtual void setFilter(const Texture::Filter &f) = 0;
void setFilter(const Texture::Filter &f);
const Texture::Filter &getFilter() const;
// Extra font metrics
@@ -172,16 +173,20 @@ public:
uint32 getTextureCacheID() const;
// Implements Volatile.
bool loadVolatile() override;
void unloadVolatile() override;
static bool getConstant(const char *in, AlignMode &out);
static bool getConstant(AlignMode in, const char *&out);
static int fontCount;
protected:
private:
struct Glyph
{
ptrdiff_t texture;
Texture *texture;
int spacing;
GlyphVertex vertices[4];
};
@@ -192,8 +197,7 @@ protected:
int height;
};
virtual void createTexture() = 0;
virtual void uploadGlyphToTexture(font::GlyphData *data, Glyph &glyph) = 0;
void createTexture();
TextureSize getNextTextureSize() const;
love::font::GlyphData *getRasterizerGlyphData(uint32 glyph);
@@ -210,6 +214,8 @@ protected:
int textureWidth;
int textureHeight;
std::vector<StrongRef<love::graphics::Image>> images;
// maps glyphs to glyph texture information
std::unordered_map<uint32, Glyph> glyphs;
@@ -226,15 +232,15 @@ protected:
int rowHeight;
bool useSpacesAsTab;
// ID which is incremented when the texture cache is invalidated.
uint32 textureCacheID;
static const int TEXTURE_PADDING = 1;
// This will be used if the Rasterizer doesn't have a tab character itself.
static const int SPACES_PER_TAB = 4;
static StringMap<AlignMode, ALIGN_MAX_ENUM>::Entry alignModeEntries[];
static StringMap<AlignMode, ALIGN_MAX_ENUM> alignModes;
+50 -32
View File
@@ -25,6 +25,8 @@
#include "Polyline.h"
#include "font/Font.h"
#include "window/Window.h"
#include "Font.h"
#include "Video.h"
// C++
#include <algorithm>
@@ -152,6 +154,16 @@ Quad *Graphics::newQuad(Quad::Viewport v, double sw, double sh)
return new Quad(v, sw, sh);
}
Font *Graphics::newFont(love::font::Rasterizer *data, const Texture::Filter &filter)
{
return new Font(data, filter);
}
Video *Graphics::newVideo(love::video::VideoStream *stream, float pixeldensity)
{
return new Video(this, stream, pixeldensity);
}
bool Graphics::validateShader(bool gles, const Shader::ShaderSource &source, std::string &err)
{
return Shader::validate(this, gles, source, true, err);
@@ -179,9 +191,9 @@ int Graphics::getPixelHeight() const
double Graphics::getCurrentPixelDensity() const
{
if (states.back().canvases.size() > 0)
if (states.back().renderTargets.size() > 0)
{
love::graphics::Canvas *c = states.back().canvases[0];
love::graphics::Canvas *c = states.back().renderTargets[0].canvas;
return (double) c->getPixelHeight() / (double) c->getHeight();
}
@@ -240,7 +252,7 @@ void Graphics::restoreState(const DisplayState &s)
setFont(s.font.get());
setShader(s.shader.get());
setCanvas(s.canvases);
setCanvas(s.renderTargets);
setColorMask(s.colorMask);
setWireframe(s.wireframe);
@@ -283,12 +295,14 @@ void Graphics::restoreStateChecked(const DisplayState &s)
setFont(s.font.get());
setShader(s.shader.get());
bool canvaseschanged = s.canvases.size() != cur.canvases.size();
bool canvaseschanged = s.renderTargets.size() != cur.renderTargets.size();
if (!canvaseschanged)
{
for (size_t i = 0; i < s.canvases.size() && i < cur.canvases.size(); i++)
for (size_t i = 0; i < s.renderTargets.size() && i < cur.renderTargets.size(); i++)
{
if (s.canvases[i].get() != cur.canvases[i].get())
const auto &rt1 = s.renderTargets[i];
const auto &rt2 = cur.renderTargets[i];
if (rt1.canvas.get() != rt2.canvas.get() || rt1.slice != rt2.slice)
{
canvaseschanged = true;
break;
@@ -297,7 +311,7 @@ void Graphics::restoreStateChecked(const DisplayState &s)
}
if (canvaseschanged)
setCanvas(s.canvases);
setCanvas(s.renderTargets);
if (s.colorMask != cur.colorMask)
setColorMask(s.colorMask);
@@ -386,47 +400,47 @@ love::graphics::Shader *Graphics::getShader() const
return states.back().shader.get();
}
void Graphics::setCanvas(Canvas *canvas)
void Graphics::setCanvas(RenderTarget rt)
{
if (canvas == nullptr)
if (rt.canvas == nullptr)
return setCanvas();
std::vector<Canvas *> canvases = {canvas};
setCanvas(canvases);
std::vector<RenderTarget> rts = {rt};
setCanvas(rts);
}
void Graphics::setCanvas(const std::vector<StrongRef<Canvas>> &canvases)
void Graphics::setCanvas(const std::vector<RenderTargetStrongRef> &rts)
{
std::vector<Canvas *> canvaslist;
canvaslist.reserve(canvases.size());
std::vector<RenderTarget> canvaslist;
canvaslist.reserve(rts.size());
for (const StrongRef<Canvas> &c : canvases)
canvaslist.push_back(c.get());
for (const auto &rt : rts)
canvaslist.emplace_back(rt.canvas.get(), rt.slice);
return setCanvas(canvaslist);
}
std::vector<Canvas *> Graphics::getCanvas() const
std::vector<Graphics::RenderTarget> Graphics::getCanvas() const
{
std::vector<Canvas *> canvases;
canvases.reserve(states.back().canvases.size());
std::vector<RenderTarget> rts;
rts.reserve(states.back().renderTargets.size());
for (const StrongRef<Canvas> &c : states.back().canvases)
canvases.push_back(c.get());
for (const auto &rt : states.back().renderTargets)
rts.emplace_back(rt.canvas.get(), rt.slice);
return canvases;
return rts;
}
bool Graphics::isCanvasActive() const
{
return !states.back().canvases.empty();
return !states.back().renderTargets.empty();
}
bool Graphics::isCanvasActive(love::graphics::Canvas *canvas) const
{
for (const auto &c : states.back().canvases)
for (const auto &rt : states.back().renderTargets)
{
if (c.get() == canvas)
if (rt.canvas.get() == canvas)
return true;
}
@@ -563,7 +577,7 @@ Graphics::StreamVertexData Graphics::requestStreamDraw(const StreamDrawRequest &
if (req.primitiveMode != state.primitiveMode
|| req.formats[0] != state.formats[0] || req.formats[1] != state.formats[1]
|| ((req.indexMode != TriangleIndexMode::NONE) != (state.indexCount > 0))
|| req.texture != state.texture || req.textureHandle != state.textureHandle)
|| req.texture != state.texture)
{
shouldflush = true;
}
@@ -622,7 +636,6 @@ Graphics::StreamVertexData Graphics::requestStreamDraw(const StreamDrawRequest &
state.formats[0] = req.formats[0];
state.formats[1] = req.formats[1];
state.texture = req.texture;
state.textureHandle = req.textureHandle;
}
if (shouldresize)
@@ -1358,6 +1371,8 @@ StringMap<Graphics::Feature, Graphics::FEATURE_MAX_ENUM>::Entry Graphics::featur
{ "lighten", FEATURE_LIGHTEN },
{ "fullnpot", FEATURE_FULL_NPOT },
{ "pixelshaderhighp", FEATURE_PIXEL_SHADER_HIGHP },
{ "arraytexture", FEATURE_ARRAY_TEXTURE },
{ "volumetexture", FEATURE_VOLUME_TEXTURE },
{ "glsl3", FEATURE_GLSL3 },
{ "instancing", FEATURE_INSTANCING },
};
@@ -1366,11 +1381,14 @@ StringMap<Graphics::Feature, Graphics::FEATURE_MAX_ENUM> Graphics::features(Grap
StringMap<Graphics::SystemLimit, Graphics::LIMIT_MAX_ENUM>::Entry Graphics::systemLimitEntries[] =
{
{ "pointsize", LIMIT_POINT_SIZE },
{ "texturesize", LIMIT_TEXTURE_SIZE },
{ "multicanvas", LIMIT_MULTI_CANVAS },
{ "canvasmsaa", LIMIT_CANVAS_MSAA },
{ "anisotropy", LIMIT_ANISOTROPY },
{ "pointsize", LIMIT_POINT_SIZE },
{ "texturesize", LIMIT_TEXTURE_SIZE },
{ "texturelayers", LIMIT_TEXTURE_LAYERS },
{ "volumetexturesize", LIMIT_VOLUME_TEXTURE_SIZE },
{ "cubetexturesize", LIMIT_CUBE_TEXTURE_SIZE },
{ "multicanvas", LIMIT_MULTI_CANVAS },
{ "canvasmsaa", LIMIT_CANVAS_MSAA },
{ "anisotropy", LIMIT_ANISOTROPY },
};
StringMap<Graphics::SystemLimit, Graphics::LIMIT_MAX_ENUM> Graphics::systemLimits(Graphics::systemLimitEntries, sizeof(Graphics::systemLimitEntries));
+37 -18
View File
@@ -177,6 +177,8 @@ public:
FEATURE_LIGHTEN,
FEATURE_FULL_NPOT,
FEATURE_PIXEL_SHADER_HIGHP,
FEATURE_ARRAY_TEXTURE,
FEATURE_VOLUME_TEXTURE,
FEATURE_GLSL3,
FEATURE_INSTANCING,
FEATURE_MAX_ENUM
@@ -193,6 +195,9 @@ public:
{
LIMIT_POINT_SIZE,
LIMIT_TEXTURE_SIZE,
LIMIT_VOLUME_TEXTURE_SIZE,
LIMIT_CUBE_TEXTURE_SIZE,
LIMIT_TEXTURE_LAYERS,
LIMIT_MULTI_CANVAS,
LIMIT_CANVAS_MSAA,
LIMIT_ANISOTROPY,
@@ -262,10 +267,6 @@ public:
int vertexCount = 0;
Texture *texture = nullptr;
// FIXME: This is only needed for fonts. We should just change fonts to
// use love.graphics Images instead of raw OpenGL textures.
ptrdiff_t textureHandle = 0;
StreamDrawRequest()
{
// VS2013 can't initialize arrays in the above manner...
@@ -312,29 +313,48 @@ public:
Reference *ref;
};
struct RenderTarget
{
Canvas *canvas = nullptr;
int slice = 0;
RenderTarget(Canvas *canvas, int slice = 0)
: canvas(canvas)
, slice(slice)
{}
};
struct RenderTargetStrongRef
{
StrongRef<Canvas> canvas;
int slice = 0;
RenderTargetStrongRef(Canvas *canvas, int slice = 0)
: canvas(canvas)
, slice(slice)
{}
};
Graphics();
virtual ~Graphics();
// Implements Module.
virtual ModuleType getModuleType() const { return M_GRAPHICS; }
virtual Image *newImage(const std::vector<love::image::ImageData *> &data, const Image::Settings &settings) = 0;
virtual Image *newImage(const std::vector<love::image::CompressedImageData *> &cdata, const Image::Settings &settings) = 0;
virtual Image *newImage(const Image::Slices &data, const Image::Settings &settings) = 0;
virtual Image *newImage(TextureType textype, PixelFormat format, int width, int height, int slices, const Image::Settings &settings) = 0;
Quad *newQuad(Quad::Viewport v, double sw, double sh);
Font *newFont(love::font::Rasterizer *data, const Texture::Filter &filter = Texture::defaultFilter);
Video *newVideo(love::video::VideoStream *stream, float pixeldensity);
virtual SpriteBatch *newSpriteBatch(Texture *texture, int size, vertex::Usage usage) = 0;
virtual ParticleSystem *newParticleSystem(Texture *texture, int size) = 0;
virtual Font *newFont(love::font::Rasterizer *data, const Texture::Filter &filter = Texture::defaultFilter) = 0;
virtual Canvas *newCanvas(int width, int height, const Canvas::Settings &settings) = 0;
virtual Canvas *newCanvas(const Canvas::Settings &settings) = 0;
virtual Shader *newShader(const Shader::ShaderSource &source) = 0;
virtual Video *newVideo(love::video::VideoStream *stream, float pixeldensity) = 0;
virtual Buffer *newBuffer(size_t size, const void *data, BufferType type, vertex::Usage usage, uint32 mapflags) = 0;
virtual Mesh *newMesh(const std::vector<Vertex> &vertices, Mesh::DrawMode drawmode, vertex::Usage usage) = 0;
@@ -435,12 +455,12 @@ public:
Shader *getShader() const;
void setCanvas(Canvas *canvas);
virtual void setCanvas(const std::vector<Canvas *> &canvases) = 0;
void setCanvas(const std::vector<StrongRef<Canvas>> &canvases);
void setCanvas(RenderTarget rt);
virtual void setCanvas(const std::vector<RenderTarget> &rts) = 0;
void setCanvas(const std::vector<RenderTargetStrongRef> &rts);
virtual void setCanvas() = 0;
std::vector<Canvas *> getCanvas() const;
std::vector<RenderTarget> getCanvas() const;
bool isCanvasActive() const;
bool isCanvasActive(Canvas *canvas) const;
@@ -790,7 +810,7 @@ protected:
StrongRef<Font> font;
StrongRef<Shader> shader;
std::vector<StrongRef<Canvas>> canvases;
std::vector<RenderTargetStrongRef> renderTargets;
ColorMask colorMask = ColorMask(true, true, true, true);
@@ -810,7 +830,6 @@ protected:
vertex::PrimitiveMode primitiveMode = vertex::PrimitiveMode::TRIANGLES;
vertex::CommonFormat formats[2];
StrongRef<Texture> texture;
ptrdiff_t textureHandle = 0;
int vertexCount = 0;
int indexCount = 0;
+162 -14
View File
@@ -19,6 +19,10 @@
**/
#include "Image.h"
#include "Graphics.h"
// C++
#include <algorithm>
namespace love
{
@@ -29,9 +33,16 @@ love::Type Image::type("Image", &Texture::type);
int Image::imageCount = 0;
Image::Image(const Settings &settings)
: settings(settings)
Image::Image(const Slices &data, const Settings &settings, bool validatedata)
: Texture(data.getTextureType())
, settings(settings)
, data(data)
, mipmapsType(settings.mipmaps ? MIPMAPS_GENERATED : MIPMAPS_NONE)
, sRGB(isGammaCorrect() && !settings.linear)
{
if (validatedata && data.validate() == MIPMAPS_DATA)
mipmapsType = MIPMAPS_DATA;
++imageCount;
}
@@ -40,29 +51,166 @@ Image::~Image()
--imageCount;
}
const Image::Settings &Image::getFlags() const
Image::Slices::Slices(TextureType textype)
: textureType(textype)
{
return settings;
}
bool Image::getConstant(const char *in, SettingType &out)
void Image::Slices::clear()
{
return settingTypes.find(in, out);
data.clear();
}
bool Image::getConstant(SettingType in, const char *&out)
void Image::Slices::set(int slice, int mipmap, love::image::ImageDataBase *d)
{
return settingTypes.find(in, out);
if (textureType == TEXTURE_VOLUME)
{
if (mipmap >= (int) data.size())
data.resize(mipmap + 1);
if (slice >= (int) data[mipmap].size())
data[mipmap].resize(slice + 1);
data[mipmap][slice].set(d);
}
else
{
if (slice >= (int) data.size())
data.resize(slice + 1);
if (mipmap >= (int) data[slice].size())
data[slice].resize(mipmap + 1);
data[slice][mipmap].set(d);
}
}
StringMap<Image::SettingType, Image::SETTING_MAX_ENUM>::Entry Image::settingTypeEntries[] =
love::image::ImageDataBase *Image::Slices::get(int slice, int mipmap) const
{
{ "mipmaps", SETTING_MIPMAPS },
{ "linear", SETTING_LINEAR },
{ "pixeldensity", SETTING_PIXELDENSITY },
};
if (slice < 0 || slice >= getSliceCount(mipmap))
return nullptr;
StringMap<Image::SettingType, Image::SETTING_MAX_ENUM> Image::settingTypes(Image::settingTypeEntries, sizeof(Image::settingTypeEntries));
if (mipmap < 0 || mipmap >= getMipmapCount(slice))
return nullptr;
if (textureType == TEXTURE_VOLUME)
return data[mipmap][slice].get();
else
return data[slice][mipmap].get();
}
void Image::Slices::add(love::image::CompressedImageData *cdata, int startslice, int startmip, bool addallslices, bool addallmips)
{
int slicecount = addallslices ? cdata->getSliceCount() : 1;
int mipcount = addallmips ? cdata->getMipmapCount() : 1;
for (int mip = 0; mip < mipcount; mip++)
{
for (int slice = 0; slice < slicecount; slice++)
set(startslice + slice, startmip + mip, cdata->getSlice(slice, mip));
}
}
int Image::Slices::getSliceCount(int mip) const
{
if (textureType == TEXTURE_VOLUME)
{
if (mip < 0 || mip >= (int) data.size())
return 0;
return (int) data[mip].size();
}
else
return (int) data.size();
}
int Image::Slices::getMipmapCount(int slice) const
{
if (textureType == TEXTURE_VOLUME)
return (int) data.size();
else
{
if (slice < 0 || slice >= (int) data.size())
return 0;
return data[slice].size();
}
}
Image::MipmapsType Image::Slices::validate() const
{
int slicecount = getSliceCount();
int mipcount = getMipmapCount(0);
if (slicecount == 0 || mipcount == 0)
throw love::Exception("At least one ImageData or CompressedImageData is required!");
if (textureType == TEXTURE_CUBE && slicecount != 6)
throw love::Exception("Cube textures must have exactly 6 sides.");
image::ImageDataBase *firstdata = get(0, 0);
int w = firstdata->getWidth();
int h = firstdata->getHeight();
PixelFormat format = firstdata->getFormat();
int expectedmips = Texture::getMipmapCount(w, h);
if (mipcount != expectedmips && mipcount != 1)
throw love::Exception("Image does not have all required mipmap levels (expected %d, got %d)", expectedmips, mipcount);
if (textureType == TEXTURE_CUBE && w != h)
throw love::Exception("Cube images must have equal widths and heights for each cube face.");
int mipw = w;
int miph = h;
int mipslices = slicecount;
for (int mip = 0; mip < mipcount; mip++)
{
if (textureType == TEXTURE_VOLUME)
{
slicecount = getSliceCount(mip);
if (slicecount != mipslices)
throw love::Exception("Invalid number of image data layers in mipmap level %d (expected %d, got %d)", mip+1, mipslices, slicecount);
}
for (int slice = 0; slice < slicecount; slice++)
{
auto slicedata = get(slice, mip);
if (slicedata == nullptr)
throw love::Exception("Missing image data (slice %d, mipmap level %d)", slice+1, mip+1);
int realw = slicedata->getWidth();
int realh = slicedata->getHeight();
if (getMipmapCount(slice) != mipcount)
throw love::Exception("All Image layers must have the same mipmap count.");
if (mipw != realw)
throw love::Exception("Width of image data (slice %d, mipmap level %d) is incorrect (expected %d, got %d)", slice+1, mip+1, mipw, realw);
if (miph != realh)
throw love::Exception("Height of image data (slice %d, mipmap level %d) is incorrect (expected %d, got %d)", slice+1, mip+1, miph, realh);
if (format != slicedata->getFormat())
throw love::Exception("All Image slices and mipmaps must have the same pixel format.");
}
mipw = std::max(mipw / 2, 1);
miph = std::max(miph / 2, 1);
if (textureType == TEXTURE_VOLUME)
mipslices = std::max(mipslices / 2, 1);
}
if (mipcount > 1)
return MIPMAPS_DATA;
else
return MIPMAPS_NONE;
}
} // graphics
} // love
+45 -20
View File
@@ -39,12 +39,11 @@ public:
static love::Type type;
enum SettingType
enum MipmapsType
{
SETTING_MIPMAPS,
SETTING_LINEAR,
SETTING_PIXELDENSITY,
SETTING_MAX_ENUM
MIPMAPS_NONE,
MIPMAPS_DATA,
MIPMAPS_GENERATED,
};
struct Settings
@@ -54,33 +53,59 @@ public:
float pixeldensity = 1.0f;
};
Image(const Settings &settings);
struct Slices
{
public:
Slices(TextureType textype);
void clear();
void set(int slice, int mipmap, love::image::ImageDataBase *data);
love::image::ImageDataBase *get(int slice, int mipmap) const;
void add(love::image::CompressedImageData *cdata, int startslice, int startmip, bool addallslices, bool addallmips);
int getSliceCount(int mip = 0) const;
int getMipmapCount(int slice = 0) const;
MipmapsType validate() const;
TextureType getTextureType() const { return textureType; }
private:
TextureType textureType;
// For 2D/Cube/2DArray texture types, each element in the data array has
// an array of mipmap levels. For 3D texture types, each mipmap level
// has an array of layers.
std::vector<std::vector<StrongRef<love::image::ImageDataBase>>> data;
}; // Slices
virtual ~Image();
virtual const std::vector<StrongRef<love::image::ImageData>> &getImageData() const = 0;
virtual const std::vector<StrongRef<love::image::CompressedImageData>> &getCompressedData() const = 0;
virtual void setMipmapSharpness(float sharpness) = 0;
virtual float getMipmapSharpness() const = 0;
virtual void replacePixels(love::image::ImageDataBase *d, int slice, int mipmap, bool reloadmipmaps) = 0;
virtual void replacePixels(const void *data, size_t size, const Rect &rect, int slice, int mipmap, bool reloadmipmaps) = 0;
virtual bool isFormatLinear() const = 0;
virtual bool isCompressed() const = 0;
virtual bool refresh(int xoffset, int yoffset, int w, int h) = 0;
const Settings &getFlags() const;
static bool getConstant(const char *in, SettingType &out);
static bool getConstant(SettingType in, const char *&out);
virtual MipmapsType getMipmapsType() const = 0;
static int imageCount;
protected:
Image(const Slices &data, const Settings &settings, bool validatedata);
// The settings used to initialize this Image.
Settings settings;
static StringMap<SettingType, SETTING_MAX_ENUM>::Entry settingTypeEntries[];
static StringMap<SettingType, SETTING_MAX_ENUM> settingTypes;
Slices data;
MipmapsType mipmapsType;
bool sRGB;
}; // Image
+8
View File
@@ -174,6 +174,14 @@ void Shader::attachDefault()
current = nullptr;
}
void Shader::checkMainTextureType(TextureType textype) const
{
const UniformInfo *info = getUniformInfo(BUILTIN_TEXTURE_MAIN);
if (info != nullptr && info->textureType != TEXTURE_MAX_ENUM && info->textureType != textype)
throw love::Exception("Texture's type must match the type of the shader's main texture.");
}
bool Shader::validate(Graphics *gfx, bool gles, const ShaderSource &source, bool checkWithDefaults, std::string &err)
{
if (source.vertex.empty() && source.pixel.empty())
+9 -7
View File
@@ -119,6 +119,7 @@ public:
};
UniformType baseType;
TextureType textureType;
std::string name;
union
@@ -144,11 +145,8 @@ public:
/**
* Binds this Shader's program to be used when rendering.
*
* @param temporary True if we just want to send values to the shader with
* no intention of rendering.
**/
virtual void attach(bool temporary = false) = 0;
virtual void attach() = 0;
/**
* Attach the default shader.
@@ -161,9 +159,11 @@ public:
virtual std::string getWarnings() const = 0;
virtual const UniformInfo *getUniformInfo(const std::string &name) const = 0;
virtual void updateUniform(const UniformInfo *info, int count, bool internalUpdate = false) = 0;
virtual const UniformInfo *getUniformInfo(BuiltinUniform builtin) const = 0;
virtual void sendTextures(const UniformInfo *info, Texture **textures, int count, bool internalUpdate = false) = 0;
virtual void updateUniform(const UniformInfo *info, int count) = 0;
virtual void sendTextures(const UniformInfo *info, Texture **textures, int count) = 0;
/**
* Gets whether a uniform with the specified name exists and is actively
@@ -174,7 +174,9 @@ public:
/**
* Sets the textures used when rendering a video. For internal use only.
**/
virtual void setVideoTextures(ptrdiff_t ytexture, ptrdiff_t cbtexture, ptrdiff_t crtexture) = 0;
virtual void setVideoTextures(Texture *ytexture, Texture *cbtexture, Texture *crtexture) = 0;
void checkMainTextureType(TextureType textype) const;
virtual ptrdiff_t getHandle() const = 0;
+111 -4
View File
@@ -18,9 +18,25 @@
* 3. This notice may not be removed or altered from any source distribution.
**/
// LOVE
#include "common/config.h"
#include "Texture.h"
#include "Graphics.h"
// C
#include <cmath>
#include <algorithm>
#ifdef LOVE_ANDROID
// log2 is not declared in the math.h shipped with the Android NDK
static inline double log2(double n)
{
// log(n)/log(2) is log2.
return std::log(n) / std::log(2);
}
#endif
namespace love
{
namespace graphics
@@ -32,14 +48,19 @@ Texture::Filter Texture::defaultFilter;
Texture::FilterMode Texture::defaultMipmapFilter = Texture::FILTER_LINEAR;
float Texture::defaultMipmapSharpness = 0.0f;
Texture::Texture()
: format(PIXELFORMAT_UNKNOWN)
Texture::Texture(TextureType texType)
: texType(texType)
, format(PIXELFORMAT_UNKNOWN)
, width(0)
, height(0)
, depth(1)
, layers(1)
, mipmapCount(1)
, pixelWidth(0)
, pixelHeight(0)
, filter(defaultFilter)
, wrap()
, mipmapSharpness(defaultMipmapSharpness)
, vertices()
{
}
@@ -48,6 +69,39 @@ Texture::~Texture()
{
}
void Texture::initVertices()
{
for (int i = 0; i < 4; i++)
vertices[i].color = Color(255, 255, 255, 255);
// Vertices are ordered for use with triangle strips:
// 0---2
// | / |
// 1---3
vertices[0].x = 0.0f;
vertices[0].y = 0.0f;
vertices[1].x = 0.0f;
vertices[1].y = (float) height;
vertices[2].x = (float) width;
vertices[2].y = 0.0f;
vertices[3].x = (float) width;
vertices[3].y = (float) height;
vertices[0].s = 0.0f;
vertices[0].t = 0.0f;
vertices[1].s = 0.0f;
vertices[1].t = 1.0f;
vertices[2].s = 1.0f;
vertices[2].t = 0.0f;
vertices[3].s = 1.0f;
vertices[3].t = 1.0f;
}
TextureType Texture::getTextureType() const
{
return texType;
}
PixelFormat Texture::getPixelFormat() const
{
return format;
@@ -65,6 +119,9 @@ void Texture::drawq(Graphics *gfx, Quad *quad, const Matrix4 &m)
void Texture::drawv(Graphics *gfx, const Matrix4 &localTransform, const Vertex *v)
{
if (Shader::current)
Shader::current->checkMainTextureType(texType);
Matrix4 t(gfx->getTransform(), localTransform);
Vertex verts[4] = {v[0], v[1], v[2], v[3]};
@@ -95,6 +152,21 @@ int Texture::getHeight() const
return height;
}
int Texture::getDepth() const
{
return depth;
}
int Texture::getLayerCount() const
{
return layers;
}
int Texture::getMipmapCount() const
{
return mipmapCount;
}
int Texture::getPixelWidth() const
{
return pixelWidth;
@@ -120,6 +192,11 @@ const Texture::Wrap &Texture::getWrap() const
return wrap;
}
float Texture::getMipmapSharpness() const
{
return mipmapSharpness;
}
const Vertex *Texture::getVertices() const
{
return vertices;
@@ -142,12 +219,32 @@ bool Texture::validateFilter(const Filter &f, bool mipmapsAllowed)
return true;
}
int Texture::getMipmapCount(int w, int h)
{
return (int) log2(std::max(w, h)) + 1;
}
int Texture::getMipmapCount(int w, int h, int d)
{
return (int) log2(std::max(std::max(w, h), d)) + 1;
}
bool Texture::getConstant(const char *in, TextureType &out)
{
return texTypes.find(in, out);
}
bool Texture::getConstant(TextureType in, const char *&out)
{
return texTypes.find(in, out);
}
bool Texture::getConstant(const char *in, FilterMode &out)
{
return filterModes.find(in, out);
}
bool Texture::getConstant(FilterMode in, const char *&out)
bool Texture::getConstant(FilterMode in, const char *&out)
{
return filterModes.find(in, out);
}
@@ -157,11 +254,21 @@ bool Texture::getConstant(const char *in, WrapMode &out)
return wrapModes.find(in, out);
}
bool Texture::getConstant(WrapMode in, const char *&out)
bool Texture::getConstant(WrapMode in, const char *&out)
{
return wrapModes.find(in, out);
}
StringMap<TextureType, TEXTURE_MAX_ENUM>::Entry Texture::texTypeEntries[] =
{
{ "2d", TEXTURE_2D },
{ "volume", TEXTURE_VOLUME },
{ "array", TEXTURE_2D_ARRAY },
{ "cube", TEXTURE_CUBE },
};
StringMap<TextureType, TEXTURE_MAX_ENUM> Texture::texTypes(Texture::texTypeEntries, sizeof(Texture::texTypeEntries));
StringMap<Texture::FilterMode, Texture::FILTER_MAX_ENUM>::Entry Texture::filterModeEntries[] =
{
{ "linear", FILTER_LINEAR },
+52 -5
View File
@@ -25,6 +25,7 @@
#include "common/StringMap.h"
#include "common/math.h"
#include "common/pixelformat.h"
#include "common/Exception.h"
#include "Drawable.h"
#include "Quad.h"
#include "vertex.h"
@@ -37,6 +38,25 @@ namespace love
namespace graphics
{
class Graphics;
enum TextureType
{
TEXTURE_2D,
TEXTURE_VOLUME,
TEXTURE_2D_ARRAY,
TEXTURE_CUBE,
TEXTURE_MAX_ENUM
};
class TextureTooLargeException : public love::Exception
{
public:
TextureTooLargeException(const char *dimname, int pix)
: Exception("Cannot create texture: %s of %d pixels is too large for this system.", dimname, pix)
{}
};
/**
* Base class for 2D textures. All textures can be drawn with Quads, have a
* width and height, and have filter and wrap modes.
@@ -76,9 +96,10 @@ public:
{
WrapMode s = WRAP_CLAMP;
WrapMode t = WRAP_CLAMP;
WrapMode r = WRAP_CLAMP;
};
Texture();
Texture(TextureType texType);
virtual ~Texture();
static Filter defaultFilter;
@@ -93,10 +114,14 @@ public:
**/
void drawq(Graphics *gfx, Quad *quad, const Matrix4 &m);
TextureType getTextureType() const;
PixelFormat getPixelFormat() const;
virtual int getWidth() const;
virtual int getHeight() const;
int getWidth() const;
int getHeight() const;
int getDepth() const;
int getLayerCount() const;
int getMipmapCount() const;
virtual int getPixelWidth() const;
virtual int getPixelHeight() const;
@@ -109,37 +134,59 @@ public:
virtual bool setWrap(const Wrap &w) = 0;
virtual const Wrap &getWrap() const;
// Sets the mipmap texture LOD bias (sharpness) value.
virtual bool setMipmapSharpness(float sharpness) = 0;
float getMipmapSharpness() const;
virtual const Vertex *getVertices() const;
virtual ptrdiff_t getHandle() const = 0;
static bool validateFilter(const Filter &f, bool mipmapsAllowed);
static int getMipmapCount(int w, int h);
static int getMipmapCount(int w, int h, int d);
static bool getConstant(const char *in, TextureType &out);
static bool getConstant(TextureType in, const char *&out);
static bool getConstant(const char *in, FilterMode &out);
static bool getConstant(FilterMode in, const char *&out);
static bool getConstant(FilterMode in, const char *&out);
static bool getConstant(const char *in, WrapMode &out);
static bool getConstant(WrapMode in, const char *&out);
static bool getConstant(WrapMode in, const char *&out);
protected:
void initVertices();
virtual void drawv(Graphics *gfx, const Matrix4 &localTransform, const Vertex *v);
TextureType texType;
PixelFormat format;
int width;
int height;
int depth;
int layers;
int mipmapCount;
int pixelWidth;
int pixelHeight;
Filter filter;
Wrap wrap;
float mipmapSharpness;
Vertex vertices[4];
private:
static StringMap<TextureType, TEXTURE_MAX_ENUM>::Entry texTypeEntries[];
static StringMap<TextureType, TEXTURE_MAX_ENUM> texTypes;
static StringMap<FilterMode, FILTER_MAX_ENUM>::Entry filterModeEntries[];
static StringMap<FilterMode, FILTER_MAX_ENUM> filterModes;
+51 -5
View File
@@ -31,14 +31,12 @@ namespace graphics
love::Type Video::type("Video", &Drawable::type);
Video::Video(love::video::VideoStream *stream, float pixeldensity)
Video::Video(Graphics *gfx, love::video::VideoStream *stream, float pixeldensity)
: stream(stream)
, width(stream->getWidth() / pixeldensity)
, height(stream->getHeight() / pixeldensity)
, filter(Texture::defaultFilter)
{
textureHandles[2] = textureHandles[1] = textureHandles[0] = 0;
filter.mipmap = Texture::FILTER_NONE;
stream->fillBackBuffer();
@@ -67,6 +65,33 @@ Video::Video(love::video::VideoStream *stream, float pixeldensity)
vertices[2].t = 0.0f;
vertices[3].s = 1.0f;
vertices[3].t = 1.0f;
// Create the textures using the initial frame data.
auto frame = (const love::video::VideoStream::Frame*) stream->getFrontBuffer();
int widths[3] = {frame->yw, frame->cw, frame->cw};
int heights[3] = {frame->yh, frame->ch, frame->ch};
const unsigned char *data[3] = {frame->yplane, frame->cbplane, frame->crplane};
Texture::Wrap wrap; // Clamp wrap mode.
Image::Settings settings;
for (int i = 0; i < 3; i++)
{
Image *img = gfx->newImage(TEXTURE_2D, PIXELFORMAT_R8, widths[i], heights[i], 1, settings);
img->setFilter(filter);
img->setWrap(wrap);
size_t bpp = getPixelFormatSize(PIXELFORMAT_R8);
size_t size = bpp * widths[i] * heights[i];
Rect rect = {0, 0, widths[i], heights[i]};
img->replacePixels(data[i], size, rect, 0, 0, false);
images[i].set(img, Acquire::NORETAIN);
}
}
Video::~Video()
@@ -93,7 +118,7 @@ void Video::draw(Graphics *gfx, const Matrix4 &m)
shader = Shader::defaultVideoShader;
}
shader->setVideoTextures(textureHandles[0], textureHandles[1], textureHandles[2]);
shader->setVideoTextures(images[0], images[1], images[2]);
Graphics::StreamDrawRequest req;
req.formats[0] = vertex::CommonFormat::XYf_STf_RGBAub;
@@ -129,7 +154,20 @@ void Video::update()
if (bufferschanged)
{
auto frame = (const love::video::VideoStream::Frame*) stream->getFrontBuffer();
uploadFrame(frame);
int widths[3] = {frame->yw, frame->cw, frame->cw};
int heights[3] = {frame->yh, frame->ch, frame->ch};
const unsigned char *data[3] = {frame->yplane, frame->cbplane, frame->crplane};
for (int i = 0; i < 3; i++)
{
size_t bpp = getPixelFormatSize(PIXELFORMAT_R8);
size_t size = bpp * widths[i] * heights[i];
Rect rect = {0, 0, widths[i], heights[i]};
images[i]->replacePixels(data[i], size, rect, 0, 0, false);
}
}
}
@@ -163,6 +201,14 @@ int Video::getPixelHeight() const
return stream->getHeight();
}
void Video::setFilter(const Texture::Filter &f)
{
for (const auto &image : images)
image->setFilter(f);
filter = f;
}
const Texture::Filter &Video::getFilter() const
{
return filter;
+8 -10
View File
@@ -23,7 +23,7 @@
// LOVE
#include "common/math.h"
#include "Drawable.h"
#include "Texture.h"
#include "Image.h"
#include "vertex.h"
#include "video/VideoStream.h"
#include "audio/Source.h"
@@ -33,13 +33,15 @@ namespace love
namespace graphics
{
class Graphics;
class Video : public Drawable
{
public:
static love::Type type;
Video(love::video::VideoStream *stream, float pixeldensity = 1.0f);
Video(Graphics *gfx, love::video::VideoStream *stream, float pixeldensity = 1.0f);
virtual ~Video();
// Drawable
@@ -56,12 +58,12 @@ public:
int getPixelWidth() const;
int getPixelHeight() const;
virtual void setFilter(const Texture::Filter &f) = 0;
void setFilter(const Texture::Filter &f);
const Texture::Filter &getFilter() const;
protected:
private:
virtual void uploadFrame(const love::video::VideoStream::Frame *frame) = 0;
void update();
StrongRef<love::video::VideoStream> stream;
@@ -72,11 +74,7 @@ protected:
Vertex vertices[4];
ptrdiff_t textureHandles[3];
private:
void update();
StrongRef<Image> images[3];
StrongRef<love::audio::Source> source;
}; // Video
+150 -64
View File
@@ -30,7 +30,7 @@ namespace graphics
namespace opengl
{
static GLenum createFBO(GLuint &framebuffer, GLuint texture)
static GLenum createFBO(GLuint &framebuffer, TextureType texType, GLuint texture, int layers, bool initialize)
{
// get currently bound fbo to reset to it later
GLuint current_fbo = gl.getFramebuffer(OpenGL::FRAMEBUFFER_ALL);
@@ -40,11 +40,27 @@ static GLenum createFBO(GLuint &framebuffer, GLuint texture)
if (texture != 0)
{
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
if (initialize)
{
int faces = texType == TEXTURE_CUBE ? 6 : 1;
// Initialize the texture to transparent black.
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT);
// 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 face = faces - 1; face >= 0; face--)
{
gl.framebufferTexture(GL_COLOR_ATTACHMENT0, texType, texture, 0, layer, face);
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT);
}
}
}
else
{
gl.framebufferTexture(GL_COLOR_ATTACHMENT0, texType, texture, 0, 0, 0);
}
}
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
@@ -96,46 +112,39 @@ static bool createMSAABuffer(int width, int height, int &samples, PixelFormat pi
return status == GL_FRAMEBUFFER_COMPLETE && samples > 1;
}
Canvas::Canvas(int width, int height, const Settings &settings)
: settings(settings)
Canvas::Canvas(const Settings &settings)
: love::graphics::Canvas(settings.type)
, fbo(0)
, texture(0)
, msaa_buffer(0)
, actual_samples(0)
, texture_memory(0)
{
this->width = width;
this->height = height;
this->width = settings.width;
this->height = settings.height;
this->pixelWidth = (int) ((width * settings.pixeldensity) + 0.5);
this->pixelHeight = (int) ((height * settings.pixeldensity) + 0.5);
// Vertices are ordered for use with triangle strips:
// 0---2
// | / |
// 1---3
// world coordinates
vertices[0].x = 0;
vertices[0].y = 0;
vertices[1].x = 0;
vertices[1].y = (float) height;
vertices[2].x = (float) width;
vertices[2].y = 0;
vertices[3].x = (float) width;
vertices[3].y = (float) height;
if (texType == TEXTURE_VOLUME)
this->depth = settings.layers;
else if (texType == TEXTURE_2D_ARRAY)
this->layers = settings.layers;
else
this->layers = 1;
// texture coordinates
vertices[0].s = 0;
vertices[0].t = 0;
vertices[1].s = 0;
vertices[1].t = 1;
vertices[2].s = 1;
vertices[2].t = 0;
vertices[3].s = 1;
vertices[3].t = 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.");
this->format = getSizedFormat(settings.format);
initVertices();
loadVolatile();
if (status != GL_FRAMEBUFFER_COMPLETE)
throw love::Exception("Cannot create Canvas: %s", OpenGL::framebufferStatusString(status));
}
Canvas::~Canvas()
@@ -148,41 +157,91 @@ bool Canvas::loadVolatile()
if (texture != 0)
return true;
if (!Canvas::isSupported())
throw love::Exception("Canvases are not supported by your OpenGL drivers!");
if (!Canvas::isFormatSupported(format))
{
const char *fstr = "rgba8";
love::getConstant(Canvas::getSizedFormat(format), fstr);
throw love::Exception("The %s canvas format is not supported by your OpenGL drivers.", fstr);
}
if (settings.msaa > 1 && texType != TEXTURE_2D)
throw love::Exception("MSAA is only supported for 2D texture types.");
if (!gl.isTextureTypeSupported(texType))
{
const char *textypestr = "unknown";
getConstant(texType, textypestr);
throw love::Exception("%s textures are not supported on this system!", textypestr);
}
switch (texType)
{
case TEXTURE_2D:
if (pixelWidth > gl.getMax2DTextureSize())
throw TextureTooLargeException("width", pixelWidth);
else if (pixelHeight > gl.getMax2DTextureSize())
throw TextureTooLargeException("height", pixelHeight);
break;
case TEXTURE_VOLUME:
if (pixelWidth > gl.getMax3DTextureSize())
throw TextureTooLargeException("width", pixelWidth);
else if (pixelHeight > gl.getMax3DTextureSize())
throw TextureTooLargeException("height", pixelHeight);
else if (depth > gl.getMax3DTextureSize())
throw TextureTooLargeException("depth", depth);
break;
case TEXTURE_2D_ARRAY:
if (pixelWidth > gl.getMax2DTextureSize())
throw TextureTooLargeException("width", pixelWidth);
else if (pixelHeight > gl.getMax2DTextureSize())
throw TextureTooLargeException("height", pixelHeight);
else if (layers > gl.getMaxTextureLayers())
throw TextureTooLargeException("array layer count", layers);
break;
case TEXTURE_CUBE:
if (pixelWidth != pixelHeight)
throw love::Exception("Cubemap textures must have equal width and height.");
else if (pixelWidth > gl.getMaxCubeTextureSize())
throw TextureTooLargeException("width", pixelWidth);
break;
default:
break;
}
OpenGL::TempDebugGroup debuggroup("Canvas load");
fbo = texture = 0;
msaa_buffer = 0;
status = GL_FRAMEBUFFER_COMPLETE;
// glTexImage2D is guaranteed to error in this case.
if (pixelWidth > gl.getMaxTextureSize() || pixelHeight > gl.getMaxTextureSize())
{
status = GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT;
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);
glGenTextures(1, &texture);
gl.bindTextureToUnit(texture, 0, false);
gl.bindTextureToUnit(this, 0, false);
GLenum gltype = OpenGL::getGLTextureType(texType);
if (GLAD_ANGLE_texture_usage)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_USAGE_ANGLE, GL_FRAMEBUFFER_ATTACHMENT_ANGLE);
glTexParameteri(gltype, GL_TEXTURE_USAGE_ANGLE, GL_FRAMEBUFFER_ATTACHMENT_ANGLE);
setFilter(filter);
setWrap(wrap);
bool unusedSRGB = false;
OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(format, false, unusedSRGB);
while (glGetError() != GL_NO_ERROR)
/* Clear the error buffer. */;
glTexImage2D(GL_TEXTURE_2D, 0, fmt.internalformat, pixelWidth, pixelHeight,
0, fmt.externalformat, fmt.type, nullptr);
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)
{
@@ -193,7 +252,7 @@ bool Canvas::loadVolatile()
}
// Create a canvas-local FBO used for glReadPixels as well as MSAA blitting.
status = createFBO(fbo, texture);
status = createFBO(fbo, texType, texture, texType == TEXTURE_VOLUME ? depth : layers, true);
if (status != GL_FRAMEBUFFER_COMPLETE)
{
@@ -207,7 +266,7 @@ bool Canvas::loadVolatile()
actual_samples = settings.msaa == 1 ? 0 : settings.msaa;
if (actual_samples > 0 && !createMSAABuffer(width, height, actual_samples, format, msaa_buffer))
if (actual_samples > 0 && !createMSAABuffer(pixelWidth, pixelHeight, actual_samples, format, msaa_buffer))
actual_samples = 0;
size_t prevmemsize = texture_memory;
@@ -246,49 +305,66 @@ void Canvas::setFilter(const Texture::Filter &f)
throw love::Exception("Invalid texture filter.");
filter = f;
gl.bindTextureToUnit(texture, 0, false);
gl.setTextureFilter(filter);
gl.bindTextureToUnit(this, 0, false);
gl.setTextureFilter(texType, filter);
}
bool Canvas::setWrap(const Texture::Wrap &w)
{
bool success = true;
bool forceclamp = texType == TEXTURE_CUBE;
wrap = w;
// If we only have limited NPOT support then the wrap mode must be CLAMP.
if ((GLAD_ES_VERSION_2_0 && !(GLAD_ES_VERSION_3_0 || GLAD_OES_texture_npot))
&& (pixelWidth != nextP2(pixelWidth) || pixelHeight != nextP2(pixelHeight)))
&& (pixelWidth != nextP2(pixelWidth) || pixelHeight != nextP2(pixelHeight) || depth != nextP2(depth)))
{
if (wrap.s != WRAP_CLAMP || wrap.t != WRAP_CLAMP)
forceclamp = true;
}
if (forceclamp)
{
if (wrap.s != WRAP_CLAMP || wrap.t != WRAP_CLAMP || wrap.r != WRAP_CLAMP)
success = false;
// If we only have limited NPOT support then the wrap mode must be CLAMP.
wrap.s = wrap.t = WRAP_CLAMP;
wrap.s = wrap.t = wrap.r = WRAP_CLAMP;
}
if (!gl.isClampZeroTextureWrapSupported())
{
if (wrap.s == WRAP_CLAMP_ZERO)
wrap.s = WRAP_CLAMP;
if (wrap.t == WRAP_CLAMP_ZERO)
wrap.t = WRAP_CLAMP;
if (wrap.s == WRAP_CLAMP_ZERO) wrap.s = WRAP_CLAMP;
if (wrap.t == WRAP_CLAMP_ZERO) wrap.t = WRAP_CLAMP;
if (wrap.r == WRAP_CLAMP_ZERO) wrap.r = WRAP_CLAMP;
}
gl.bindTextureToUnit(texture, 0, false);
gl.setTextureWrap(wrap);
gl.bindTextureToUnit(this, 0, false);
gl.setTextureWrap(texType, wrap);
return success;
}
bool Canvas::setMipmapSharpness(float /*sharpness*/)
{
return false;
}
ptrdiff_t Canvas::getHandle() const
{
return texture;
}
love::image::ImageData *Canvas::newImageData(love::image::Image *module, int x, int y, int w, int h)
love::image::ImageData *Canvas::newImageData(love::image::Image *module, int slice, int x, int y, int w, int h)
{
if (x < 0 || y < 0 || w <= 0 || h <= 0 || (x + w) > getPixelWidth() || (y + h) > getPixelHeight())
throw love::Exception("Invalid rectangle dimensions.");
if (slice < 0 || (texType == TEXTURE_VOLUME && slice >= depth)
|| (texType == TEXTURE_2D_ARRAY && slice >= layers)
|| (texType == TEXTURE_CUBE && slice >= 6))
{
throw love::Exception("Invalid slice index.");
}
Graphics *gfx = Module::getInstance<Graphics>(Module::M_GRAPHICS);
if (gfx != nullptr && gfx->isCanvasActive(this))
throw love::Exception("Canvas:newImageData cannot be called while that Canvas is currently active.");
@@ -323,8 +399,18 @@ love::image::ImageData *Canvas::newImageData(love::image::Image *module, int x,
GLuint current_fbo = gl.getFramebuffer(OpenGL::FRAMEBUFFER_ALL);
gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, getFBO());
if (slice > 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);
}
glReadPixels(x, y, w, h, fmt.externalformat, fmt.type, imagedata->getData());
if (slice > 0)
gl.framebufferTexture(GL_COLOR_ATTACHMENT0, texType, texture, 0, 0, 0);
gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, current_fbo);
return imagedata;
@@ -383,14 +469,14 @@ bool Canvas::isFormatSupported(PixelFormat format)
GLuint texture = 0;
glGenTextures(1, &texture);
gl.bindTextureToUnit(texture, 0, false);
gl.bindTextureToUnit(TEXTURE_2D, texture, 0, false);
Texture::Filter f;
f.min = f.mag = Texture::FILTER_NEAREST;
gl.setTextureFilter(f);
gl.setTextureFilter(TEXTURE_2D, f);
Texture::Wrap w;
gl.setTextureWrap(w);
gl.setTextureWrap(TEXTURE_2D, w);
bool unusedSRGB = false;
OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(format, false, unusedSRGB);
@@ -398,7 +484,7 @@ bool Canvas::isFormatSupported(PixelFormat format)
glTexImage2D(GL_TEXTURE_2D, 0, fmt.internalformat, 2, 2, 0, fmt.externalformat, fmt.type, nullptr);
GLuint fbo = 0;
supported = (createFBO(fbo, texture) == GL_FRAMEBUFFER_COMPLETE);
supported = (createFBO(fbo, TEXTURE_2D, texture, 1, false) == GL_FRAMEBUFFER_COMPLETE);
gl.deleteFramebuffer(fbo);
gl.deleteTexture(texture);
+3 -2
View File
@@ -39,7 +39,7 @@ class Canvas final : public love::graphics::Canvas, public Volatile
{
public:
Canvas(int width, int height, const Settings &settings);
Canvas(const Settings &settings);
virtual ~Canvas();
// Implements Volatile.
@@ -49,9 +49,10 @@ public:
// Implements Texture.
void setFilter(const Texture::Filter &f) override;
bool setWrap(const Texture::Wrap &w) override;
bool setMipmapSharpness(float sharpness) override;
ptrdiff_t getHandle() const override;
love::image::ImageData *newImageData(love::image::Image *module, int x, int y, int w, int h) override;
love::image::ImageData *newImageData(love::image::Image *module, int slice, int x, int y, int w, int h) override;
int getMSAA() const override
{
-190
View File
@@ -1,190 +0,0 @@
/**
* Copyright (c) 2006-2017 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
**/
// LOVE
#include "Font.h"
#include "graphics/Graphics.h"
namespace love
{
namespace graphics
{
namespace opengl
{
Font::Font(love::font::Rasterizer *r, const Texture::Filter &f)
: love::graphics::Font(r, f)
, textureMemorySize(0)
{
loadVolatile();
}
Font::~Font()
{
unloadVolatile();
}
void Font::createTexture()
{
auto gfx = Module::getInstance<graphics::Graphics>(Module::M_GRAPHICS);
gfx->flushStreamDraws();
OpenGL::TempDebugGroup debuggroup("Font create texture");
size_t bpp = getPixelFormatSize(pixelFormat);
size_t prevmemsize = textureMemorySize;
if (prevmemsize > 0)
{
textureMemorySize -= (textureWidth * textureHeight * bpp);
gl.updateTextureMemorySize(prevmemsize, textureMemorySize);
}
GLuint t = 0;
TextureSize size = {textureWidth, textureHeight};
TextureSize nextsize = getNextTextureSize();
bool recreatetexture = false;
// If we have an existing texture already, we'll try replacing it with a
// larger-sized one rather than creating a second one. Having a single
// texture reduces texture switches and draw calls when rendering.
if ((nextsize.width > size.width || nextsize.height > size.height)
&& !textures.empty())
{
recreatetexture = true;
size = nextsize;
t = textures.back();
}
else
glGenTextures(1, &t);
gl.bindTextureToUnit(t, 0, false);
gl.setTextureFilter(filter);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
bool sRGB = isGammaCorrect();
OpenGL::TextureFormat fmt = gl.convertPixelFormat(pixelFormat, false, sRGB);
if (fmt.swizzled)
{
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, fmt.swizzle[0]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_G, fmt.swizzle[1]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, fmt.swizzle[2]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_A, fmt.swizzle[3]);
}
// Initialize the texture with transparent black.
std::vector<GLubyte> emptydata(size.width * size.height * bpp, 0);
// Clear errors before initializing.
while (glGetError() != GL_NO_ERROR);
glTexImage2D(GL_TEXTURE_2D, 0, fmt.internalformat, size.width, size.height,
0, fmt.externalformat, fmt.type, &emptydata[0]);
if (glGetError() != GL_NO_ERROR)
{
if (!recreatetexture)
gl.deleteTexture(t);
throw love::Exception("Could not create font texture!");
}
textureWidth = size.width;
textureHeight = size.height;
rowHeight = textureX = textureY = TEXTURE_PADDING;
prevmemsize = textureMemorySize;
textureMemorySize += emptydata.size();
gl.updateTextureMemorySize(prevmemsize, textureMemorySize);
// Re-add the old glyphs if we re-created the existing texture object.
if (recreatetexture)
{
textureCacheID++;
std::vector<uint32> glyphstoadd;
for (const auto &glyphpair : glyphs)
glyphstoadd.push_back(glyphpair.first);
glyphs.clear();
for (uint32 g : glyphstoadd)
addGlyph(g);
}
else
textures.push_back(t);
}
void Font::uploadGlyphToTexture(font::GlyphData *gd, Glyph &glyph)
{
bool isSRGB = isGammaCorrect();
OpenGL::TextureFormat fmt = gl.convertPixelFormat(pixelFormat, false, isSRGB);
glyph.texture = textures.back();
gl.bindTextureToUnit(glyph.texture, 0, false);
glTexSubImage2D(GL_TEXTURE_2D, 0, textureX, textureY, gd->getWidth(), gd->getHeight(),
fmt.externalformat, fmt.type, gd->getData());
}
void Font::setFilter(const Texture::Filter &f)
{
if (!Texture::validateFilter(f, false))
throw love::Exception("Invalid texture filter.");
filter = f;
for (GLuint texture : textures)
{
gl.bindTextureToUnit(texture, 0, false);
gl.setTextureFilter(filter);
}
}
bool Font::loadVolatile()
{
createTexture();
textureCacheID++;
return true;
}
void Font::unloadVolatile()
{
// nuke everything from orbit
glyphs.clear();
for (GLuint texture : textures)
gl.deleteTexture(texture);
textures.clear();
gl.updateTextureMemorySize(textureMemorySize, 0);
textureMemorySize = 0;
}
} // opengl
} // graphics
} // love
-62
View File
@@ -1,62 +0,0 @@
/**
* Copyright (c) 2006-2017 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
**/
#pragma once
// LOVE
#include "graphics/Font.h"
#include "graphics/Volatile.h"
#include "OpenGL.h"
namespace love
{
namespace graphics
{
namespace opengl
{
class Font final : public love::graphics::Font, public Volatile
{
public:
Font(love::font::Rasterizer *r, const Texture::Filter &filter);
virtual ~Font();
void setFilter(const Texture::Filter &f) override;
// Implements Volatile.
bool loadVolatile() override;
void unloadVolatile() override;
private:
void createTexture() override;
void uploadGlyphToTexture(font::GlyphData *data, Glyph &glyph) override;
// vector of packed textures
std::vector<GLuint> textures;
size_t textureMemorySize;
}; // Font
} // opengl
} // graphics
} // love
+82 -98
View File
@@ -25,12 +25,10 @@
#include "Graphics.h"
#include "font/Font.h"
#include "Font.h"
#include "StreamBuffer.h"
#include "math/MathModule.h"
#include "window/Window.h"
#include "Buffer.h"
#include "Video.h"
#include "Text.h"
#include "libraries/xxHash/xxhash.h"
@@ -103,19 +101,14 @@ love::graphics::StreamBuffer *Graphics::newStreamBuffer(BufferType type, size_t
return CreateStreamBuffer(type, size);
}
love::graphics::Image *Graphics::newImage(const std::vector<love::image::ImageData *> &data, const Image::Settings &settings)
love::graphics::Image *Graphics::newImage(const Image::Slices &data, const Image::Settings &settings)
{
return new Image(data, settings);
}
love::graphics::Image *Graphics::newImage(const std::vector<love::image::CompressedImageData *> &cdata, const Image::Settings &settings)
love::graphics::Image *Graphics::newImage(TextureType textype, PixelFormat format, int width, int height, int slices, const Image::Settings &settings)
{
return new Image(cdata, settings);
}
graphics::Font *Graphics::newFont(love::font::Rasterizer *r, const Texture::Filter &filter)
{
return new Font(r, filter);
return new Image(textype, format, width, height, slices, settings);
}
love::graphics::SpriteBatch *Graphics::newSpriteBatch(Texture *texture, int size, vertex::Usage usage)
@@ -128,35 +121,12 @@ love::graphics::ParticleSystem *Graphics::newParticleSystem(Texture *texture, in
return new ParticleSystem(this, texture, size);
}
love::graphics::Canvas *Graphics::newCanvas(int width, int height, const Canvas::Settings &settings)
love::graphics::Canvas *Graphics::newCanvas(const Canvas::Settings &settings)
{
if (!Canvas::isSupported())
throw love::Exception("Canvases are not supported by your OpenGL drivers!");
if (!Canvas::isFormatSupported(settings.format))
{
const char *fstr = "rgba8";
love::getConstant(Canvas::getSizedFormat(settings.format), fstr);
throw love::Exception("The %s canvas format is not supported by your OpenGL drivers.", fstr);
}
if (width > gl.getMaxTextureSize())
throw Exception("Cannot create canvas: width of %d pixels is too large for this system.", width);
else if (height > gl.getMaxTextureSize())
throw Exception("Cannot create canvas: height of %d pixels is too large for this system.", height);
Canvas *canvas = new Canvas(width, height, settings);
GLenum err = canvas->getStatus();
// everything ok, return canvas (early out)
if (err == GL_FRAMEBUFFER_COMPLETE)
return canvas;
canvas->release();
throw love::Exception("Cannot create Canvas: %s", OpenGL::framebufferStatusString(err));
return nullptr; // never reached
return new Canvas(settings);
}
love::graphics::Shader *Graphics::newShader(const Shader::ShaderSource &source)
{
return new Shader(source);
@@ -192,11 +162,6 @@ love::graphics::Text *Graphics::newText(graphics::Font *font, const std::vector<
return new Text(this, font, text);
}
love::graphics::Video *Graphics::newVideo(love::video::VideoStream *stream, float pixeldensity)
{
return new Video(stream, pixeldensity);
}
void Graphics::setViewportSize(int width, int height, int pixelwidth, int pixelheight)
{
this->width = width;
@@ -204,7 +169,7 @@ void Graphics::setViewportSize(int width, int height, int pixelwidth, int pixelh
this->pixelWidth = pixelwidth;
this->pixelHeight = pixelheight;
if (states.back().canvases.empty())
if (states.back().renderTargets.empty())
{
// Set the viewport to top-left corner.
gl.setViewport({0, 0, pixelwidth, pixelheight});
@@ -262,6 +227,10 @@ bool Graphics::setMode(int width, int height, int pixelwidth, int pixelheight, b
// Set pixel row alignment
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
// Always enable seamless cubemap filtering when possible.
if (GLAD_VERSION_3_2 || GLAD_ARB_seamless_cube_map)
glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS);
// Set whether drawing converts input from linear -> sRGB colorspace.
if (GLAD_VERSION_3_0 || GLAD_ARB_framebuffer_sRGB || GLAD_EXT_framebuffer_sRGB
|| GLAD_ES_VERSION_3_0 || GLAD_EXT_sRGB)
@@ -374,6 +343,9 @@ void Graphics::flushStreamDraws()
if (sbstate.vertexCount == 0 && sbstate.indexCount == 0)
return;
if (Shader::current && sbstate.texture.get())
Shader::current->checkMainTextureType(sbstate.texture->getTextureType());
OpenGL::TempDebugGroup debuggroup("Stream vertices flush and draw");
uint32 attribs = 0;
@@ -448,11 +420,7 @@ void Graphics::flushStreamDraws()
pushIdentityTransform();
gl.prepareDraw();
if (sbstate.textureHandle != 0)
gl.bindTextureToUnit((GLuint) sbstate.textureHandle, 0, false);
else
gl.bindTextureToUnit(sbstate.texture, 0, false);
gl.bindTextureToUnit(sbstate.texture, 0, false);
gl.useVertexAttribArrays(attribs);
@@ -547,21 +515,22 @@ void Graphics::setDebug(bool enable)
::printf("OpenGL debug output enabled (LOVE_GRAPHICS_DEBUG=1)\n");
}
void Graphics::setCanvas(const std::vector<love::graphics::Canvas *> &canvases)
void Graphics::setCanvas(const std::vector<RenderTarget> &rts)
{
DisplayState &state = states.back();
int ncanvases = (int) canvases.size();
int ncanvases = (int) rts.size();
if (ncanvases == 0)
return setCanvas();
if (ncanvases == (int) state.canvases.size())
if (ncanvases == (int) state.renderTargets.size())
{
bool modified = false;
for (int i = 0; i < ncanvases; i++)
{
if (canvases[i] != state.canvases[i].get())
if (rts[i].canvas != state.renderTargets[i].canvas.get()
|| rts[i].slice != state.renderTargets[i].slice)
{
modified = true;
break;
@@ -575,7 +544,7 @@ void Graphics::setCanvas(const std::vector<love::graphics::Canvas *> &canvases)
if (ncanvases > gl.getMaxRenderTargets())
throw love::Exception("This system can't simultaneously render to %d canvases.", ncanvases);
love::graphics::Canvas *firstcanvas = canvases[0];
love::graphics::Canvas *firstcanvas = rts[0].canvas;
bool multiformatsupported = Canvas::isMultiFormatMultiCanvasSupported();
PixelFormat firstformat = firstcanvas->getPixelFormat();
@@ -586,7 +555,7 @@ void Graphics::setCanvas(const std::vector<love::graphics::Canvas *> &canvases)
for (int i = 1; i < ncanvases; i++)
{
love::graphics::Canvas *c = canvases[i];
love::graphics::Canvas *c = rts[i].canvas;
if (c->getPixelWidth() != pixelwidth || c->getPixelHeight() != pixelheight)
throw love::Exception("All canvases in must have the same pixel dimensions.");
@@ -595,7 +564,7 @@ void Graphics::setCanvas(const std::vector<love::graphics::Canvas *> &canvases)
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 requested MSAA value.");
throw love::Exception("All Canvases in must have the same MSAA value.");
if (c->getPixelFormat() == PIXELFORMAT_sRGBA8)
hasSRGBcanvas = true;
@@ -605,7 +574,7 @@ void Graphics::setCanvas(const std::vector<love::graphics::Canvas *> &canvases)
endPass();
bindCachedFBO(canvases);
bindCachedFBO(rts);
gl.setViewport({0, 0, pixelwidth, pixelheight});
@@ -627,13 +596,13 @@ void Graphics::setCanvas(const std::vector<love::graphics::Canvas *> &canvases)
gl.setFramebufferSRGB(false);
}
std::vector<StrongRef<love::graphics::Canvas>> canvasrefs;
canvasrefs.reserve(canvases.size());
std::vector<RenderTargetStrongRef> canvasrefs;
canvasrefs.reserve(rts.size());
for (love::graphics::Canvas *c : canvases)
canvasrefs.push_back(c);
for (auto c : rts)
canvasrefs.emplace_back(c.canvas, c.slice);
std::swap(state.canvases, canvasrefs);
std::swap(state.renderTargets, canvasrefs);
canvasSwitchCount++;
}
@@ -642,14 +611,14 @@ void Graphics::setCanvas()
{
DisplayState &state = states.back();
if (state.canvases.empty())
if (state.renderTargets.empty())
return;
OpenGL::TempDebugGroup debuggroup("setCanvas()");
endPass();
state.canvases.clear();
state.renderTargets.clear();
gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, gl.getDefaultFBO());
@@ -682,17 +651,18 @@ void Graphics::endPass()
// Discard the stencil buffer.
discard({}, true);
auto &canvases = states.back().canvases;
auto &canvases = states.back().renderTargets;
// Resolve MSAA buffers.
if (canvases.size() > 0 && canvases[0]->getMSAA() > 1)
// 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)
{
int w = canvases[0]->getPixelWidth();
int h = canvases[0]->getPixelHeight();
int w = canvases[0].canvas->getPixelWidth();
int h = canvases[0].canvas->getPixelHeight();
for (int i = 0; i < (int) canvases.size(); i++)
{
Canvas *c = (Canvas *) canvases[i].get();
Canvas *c = (Canvas *) canvases[i].canvas.get();
glReadBuffer(GL_COLOR_ATTACHMENT0 + i);
@@ -728,7 +698,7 @@ void Graphics::clear(const std::vector<OptionalColorf> &colors)
if (colors.size() == 0)
return;
int ncanvases = (int) states.back().canvases.size();
int ncanvases = (int) states.back().renderTargets.size();
int ncolors = std::min((int) colors.size(), ncanvases);
if (ncolors <= 1 && ncanvases <= 1)
@@ -810,7 +780,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().canvases.empty() && gl.getDefaultFBO() == 0)
if (states.back().renderTargets.empty() && gl.getDefaultFBO() == 0)
{
if (colorbuffers.size() > 0 && colorbuffers[0])
attachments.push_back(GL_COLOR);
@@ -823,7 +793,7 @@ void Graphics::discard(OpenGL::FramebufferTarget target, const std::vector<bool>
}
else
{
int rendertargetcount = std::max((int) states.back().canvases.size(), 1);
int rendertargetcount = std::max((int) states.back().renderTargets.size(), 1);
for (int i = 0; i < (int) colorbuffers.size(); i++)
{
@@ -845,11 +815,10 @@ void Graphics::discard(OpenGL::FramebufferTarget target, const std::vector<bool>
glDiscardFramebufferEXT(gltarget, (GLint) attachments.size(), &attachments[0]);
}
void Graphics::bindCachedFBO(const std::vector<love::graphics::Canvas *> &canvases)
void Graphics::bindCachedFBO(const std::vector<RenderTarget> &targets)
{
int ncanvases = (int) canvases.size();
uint32 hash = XXH32(&canvases[0], sizeof(love::graphics::Canvas *) * ncanvases, 0);
int ntargets = (int) targets.size();
uint32 hash = XXH32(&targets[0], sizeof(RenderTarget) * ntargets, 0);
GLuint fbo = framebufferObjects[hash];
@@ -859,35 +828,40 @@ void Graphics::bindCachedFBO(const std::vector<love::graphics::Canvas *> &canvas
}
else
{
int w = canvases[0]->getPixelWidth();
int h = canvases[0]->getPixelHeight();
int msaa = std::max(canvases[0]->getMSAA(), 1);
int w = targets[0].canvas->getPixelWidth();
int h = targets[0].canvas->getPixelHeight();
int msaa = std::max(targets[0].canvas->getMSAA(), 1);
glGenFramebuffers(1, &fbo);
gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, fbo);
GLenum drawbuffers[MAX_COLOR_RENDER_TARGETS];
for (int i = 0; i < ncanvases; i++)
for (int i = 0; i < ntargets; i++)
{
drawbuffers[i] = GL_COLOR_ATTACHMENT0 + i;
if (msaa > 1)
{
GLuint rbo = (GLuint) canvases[i]->getMSAAHandle();
GLuint rbo = (GLuint) targets[i].canvas->getMSAAHandle();
glFramebufferRenderbuffer(GL_FRAMEBUFFER, drawbuffers[i], GL_RENDERBUFFER, rbo);
}
else
{
GLuint tex = (GLuint) canvases[i]->getHandle();
glFramebufferTexture2D(GL_FRAMEBUFFER, drawbuffers[i], GL_TEXTURE_2D, tex, 0);
GLuint tex = (GLuint) targets[i].canvas->getHandle();
TextureType textype = targets[i].canvas->getTextureType();
int layer = textype == TEXTURE_CUBE ? 0 : targets[i].slice;
int face = textype == TEXTURE_CUBE ? targets[i].slice : 0;
gl.framebufferTexture(drawbuffers[i], textype, tex, 0, layer, face);
}
}
if (ncanvases > 1)
glDrawBuffers(ncanvases, drawbuffers);
if (ntargets > 1)
glDrawBuffers(ntargets, drawbuffers);
GLuint stencil = attachCachedStencilBuffer(w, h, canvases[0]->getRequestedMSAA());
GLuint stencil = attachCachedStencilBuffer(w, h, targets[0].canvas->getRequestedMSAA());
if (stencil == 0)
{
@@ -904,7 +878,7 @@ void Graphics::bindCachedFBO(const std::vector<love::graphics::Canvas *> &canvas
const char *sstr = OpenGL::framebufferStatusString(status);
throw love::Exception("Could not create Framebuffer Object! %s", sstr);
}
framebufferObjects[hash] = fbo;
}
}
@@ -990,7 +964,7 @@ void Graphics::present(void *screenshotCallbackData)
if (!isActive())
return;
if (!states.back().canvases.empty())
if (!states.back().renderTargets.empty())
throw love::Exception("present cannot be called while a Canvas is active.");
endPass();
@@ -1029,8 +1003,8 @@ void Graphics::present(void *screenshotCallbackData)
{
gl.bindFramebuffer(OpenGL::FRAMEBUFFER_DRAW, info.info.uikit.resolveFramebuffer);
// We need to do an explicit MSAA resolve on iOS, because it uses GLES
// FBOs rather than a system framebuffer.
// We need to do an explicit MSAA resolve on iOS, because it uses
// GLES FBOs rather than a system framebuffer.
if (GLAD_ES_VERSION_3_0)
glBlitFramebuffer(0, 0, w, h, 0, 0, w, h, GL_COLOR_BUFFER_BIT, GL_NEAREST);
else if (GLAD_APPLE_framebuffer_multisample)
@@ -1122,7 +1096,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.canvases.empty());
gl.setScissor(glrect, !state.renderTargets.empty());
state.scissor = true;
state.scissorRect = rect;
@@ -1139,7 +1113,7 @@ void Graphics::setScissor()
void Graphics::drawToStencilBuffer(StencilAction action, int value)
{
if (states.back().canvases.empty() && !windowHasStencil)
if (states.back().renderTargets.empty() && !windowHasStencil)
throw love::Exception("The window must have stenciling enabled to draw to the main screen's stencil buffer.");
flushStreamDraws();
@@ -1200,7 +1174,7 @@ void Graphics::stopDrawToStencilBuffer()
void Graphics::setStencilTest(CompareMode compare, int value)
{
if (compare != COMPARE_ALWAYS && states.back().canvases.empty() && !windowHasStencil)
if (compare != COMPARE_ALWAYS && states.back().renderTargets.empty() && !windowHasStencil)
throw love::Exception("The window must have stenciling enabled to use setStencilTest on the main screen.");
DisplayState &state = states.back();
@@ -1454,15 +1428,21 @@ double Graphics::getSystemLimit(SystemLimit limittype) const
{
switch (limittype)
{
case Graphics::LIMIT_POINT_SIZE:
case LIMIT_POINT_SIZE:
return (double) gl.getMaxPointSize();
case Graphics::LIMIT_TEXTURE_SIZE:
return (double) gl.getMaxTextureSize();
case Graphics::LIMIT_MULTI_CANVAS:
case LIMIT_TEXTURE_SIZE:
return (double) gl.getMax2DTextureSize();
case LIMIT_TEXTURE_LAYERS:
return (double) gl.getMaxTextureLayers();
case LIMIT_VOLUME_TEXTURE_SIZE:
return (double) gl.getMax3DTextureSize();
case LIMIT_CUBE_TEXTURE_SIZE:
return (double) gl.getMaxCubeTextureSize();
case LIMIT_MULTI_CANVAS:
return (double) gl.getMaxRenderTargets();
case Graphics::LIMIT_CANVAS_MSAA:
case LIMIT_CANVAS_MSAA:
return (double) gl.getMaxRenderbufferSamples();
case Graphics::LIMIT_ANISOTROPY:
case LIMIT_ANISOTROPY:
return (double) gl.getMaxAnisotropy();
default:
return 0.0;
@@ -1483,6 +1463,10 @@ bool Graphics::isSupported(Feature feature) const
return GLAD_VERSION_2_0 || GLAD_ES_VERSION_3_0 || GLAD_OES_texture_npot;
case FEATURE_PIXEL_SHADER_HIGHP:
return gl.isPixelShaderHighpSupported();
case FEATURE_ARRAY_TEXTURE:
return gl.isTextureTypeSupported(TEXTURE_2D_ARRAY);
case FEATURE_VOLUME_TEXTURE:
return gl.isTextureTypeSupported(TEXTURE_VOLUME);
case FEATURE_GLSL3:
return GLAD_ES_VERSION_3_0 || gl.isCoreProfile();
case FEATURE_INSTANCING:
+5 -11
View File
@@ -61,17 +61,13 @@ public:
// Implements Module.
const char *getName() const override;
love::graphics::Image *newImage(const std::vector<love::image::ImageData *> &data, const Image::Settings &settings) override;
love::graphics::Image *newImage(const std::vector<love::image::CompressedImageData *> &cdata, const Image::Settings &settings) override;
love::graphics::Font *newFont(love::font::Rasterizer *data, const Texture::Filter &filter = Texture::defaultFilter) override;
love::graphics::Image *newImage(const Image::Slices &data, const Image::Settings &settings) override;
love::graphics::Image *newImage(TextureType textype, PixelFormat format, int width, int height, int slices, const Image::Settings &settings) override;
love::graphics::SpriteBatch *newSpriteBatch(Texture *texture, int size, vertex::Usage usage) override;
love::graphics::ParticleSystem *newParticleSystem(Texture *texture, int size) override;
love::graphics::Canvas *newCanvas(int width, int height, const Canvas::Settings &settings) override;
love::graphics::Canvas *newCanvas(const Canvas::Settings &settings) override;
love::graphics::Shader *newShader(const Shader::ShaderSource &source) override;
love::graphics::Buffer *newBuffer(size_t size, const void *data, BufferType type, vertex::Usage usage, uint32 mapflags) override;
@@ -84,8 +80,6 @@ public:
love::graphics::Text *newText(love::graphics::Font *font, const std::vector<Font::ColoredString> &text = {}) override;
love::graphics::Video *newVideo(love::video::VideoStream *stream, float pixeldensity) override;
void setViewportSize(int width, int height, int pixelwidth, int pixelheight) override;
bool setMode(int width, int height, int pixelwidth, int pixelheight, bool windowhasstencil) override;
void unSetMode() override;
@@ -103,7 +97,7 @@ public:
void setColor(Colorf c) override;
void setCanvas(const std::vector<love::graphics::Canvas *> &canvases) override;
void setCanvas(const std::vector<RenderTarget> &rts) override;
void setCanvas() override;
void setScissor(const Rect &rect) override;
@@ -149,7 +143,7 @@ private:
love::graphics::StreamBuffer *newStreamBuffer(BufferType type, size_t size) override;
void endPass();
void bindCachedFBO(const std::vector<love::graphics::Canvas *> &canvases);
void bindCachedFBO(const std::vector<RenderTarget> &targets);
void discard(OpenGL::FramebufferTarget target, const std::vector<bool> &colorbuffers, bool depthstencil);
GLuint attachCachedStencilBuffer(int w, int h, int samples);
+229 -261
View File
@@ -26,16 +26,6 @@
// STD
#include <algorithm> // for min/max
#ifdef LOVE_ANDROID
// log2 is not declared in the math.h shipped with the Android NDK
#include <cmath>
inline double log2(double n)
{
// log(n)/log(2) is log2.
return std::log(n) / std::log(2);
}
#endif
namespace love
{
namespace graphics
@@ -45,114 +35,38 @@ namespace opengl
float Image::maxMipmapSharpness = 0.0f;
static int getMipmapCount(int basewidth, int baseheight)
{
return (int) log2(std::max(basewidth, baseheight)) + 1;
}
template <typename T>
static bool verifyMipmapLevels(const std::vector<T> &miplevels)
{
int numlevels = (int) miplevels.size();
if (numlevels == 1)
return false;
int width = miplevels[0]->getWidth();
int height = miplevels[0]->getHeight();
auto format = miplevels[0]->getFormat();
int expectedlevels = getMipmapCount(width, height);
// All mip levels must be present when not using auto-generated mipmaps.
if (numlevels != expectedlevels)
throw love::Exception("Image does not have all required mipmap levels (expected %d, got %d)", expectedlevels, numlevels);
// Verify the size of each mip level.
for (int i = 1; i < numlevels; i++)
{
width = std::max(width / 2, 1);
height = std::max(height / 2, 1);
if (miplevels[i]->getWidth() != width)
throw love::Exception("Width of image mipmap level %d is incorrect (expected %d, got %d)", i+1, width, miplevels[i]->getWidth());
if (miplevels[i]->getHeight() != height)
throw love::Exception("Height of image mipmap level %d is incorrect (expected %d, got %d)", i+1, height, miplevels[i]->getHeight());
if (miplevels[i]->getFormat() != format)
throw love::Exception("All image mipmap levels must have the same format.");
}
return true;
}
Image::Image(const std::vector<love::image::ImageData *> &imagedata, const Settings &settings)
: love::graphics::Image(settings)
Image::Image(TextureType textype, PixelFormat format, int width, int height, int slices, const Settings &settings)
: love::graphics::Image(Slices(textype), settings, false)
, texture(0)
, mipmapSharpness(defaultMipmapSharpness)
, compressed(false)
, sRGB(false)
, usingDefaultTexture(false)
, textureMemorySize(0)
{
if (imagedata.empty())
throw love::Exception("");
if (isPixelFormatCompressed(format))
throw love::Exception("This constructor is only supported for non-compressed pixel formats.");
pixelWidth = imagedata[0]->getWidth();
pixelHeight = imagedata[0]->getHeight();
if (textype == TEXTURE_VOLUME)
depth = slices;
else if (textype == TEXTURE_2D_ARRAY)
layers = slices;
width = (int) (pixelWidth / settings.pixeldensity + 0.5);
height = (int) (pixelHeight / settings.pixeldensity + 0.5);
if (verifyMipmapLevels(imagedata))
this->settings.mipmaps = true;
for (const auto &id : imagedata)
data.push_back(id);
format = data[0]->getFormat();
preload();
loadVolatile();
init(format, width, height, settings);
}
Image::Image(const std::vector<love::image::CompressedImageData *> &compresseddata, const Settings &settings)
: love::graphics::Image(settings)
Image::Image(const Slices &slices, const Settings &settings)
: love::graphics::Image(slices, settings, true)
, texture(0)
, mipmapSharpness(defaultMipmapSharpness)
, compressed(true)
, sRGB(false)
, compressed(false)
, usingDefaultTexture(false)
, textureMemorySize(0)
{
pixelWidth = compresseddata[0]->getWidth(0);
pixelHeight = compresseddata[0]->getHeight(0);
if (texType == TEXTURE_2D_ARRAY)
this->layers = data.getSliceCount();
else if (texType == TEXTURE_VOLUME)
this->depth = data.getSliceCount();
width = (int) (pixelWidth / settings.pixeldensity + 0.5);
height = (int) (pixelHeight / settings.pixeldensity + 0.5);
if (verifyMipmapLevels(compresseddata))
this->settings.mipmaps = true;
else if (settings.mipmaps && getMipmapCount(pixelWidth, pixelHeight) != compresseddata[0]->getMipmapCount())
{
if (compresseddata[0]->getMipmapCount() == 1)
this->settings.mipmaps = false;
else
{
throw love::Exception("Image cannot have mipmaps: compressed image data does not have all required mipmap levels (expected %d, got %d)",
getMipmapCount(width, height),
compresseddata[0]->getMipmapCount());
}
}
for (image::CompressedImageData *cd : compresseddata)
cdata.push_back(cd);
format = cdata[0]->getFormat();
preload();
loadVolatile();
love::image::ImageDataBase *slice = data.get(0, 0);
init(slice->getFormat(), slice->getWidth(), slice->getHeight(), settings);
}
Image::~Image()
@@ -160,56 +74,41 @@ Image::~Image()
unloadVolatile();
}
void Image::preload()
void Image::init(PixelFormat fmt, int w, int h, const Settings &settings)
{
for (int i = 0; i < 4; i++)
vertices[i].color = Color(255, 255, 255, 255);
pixelWidth = w;
pixelHeight = h;
// Vertices are ordered for use with triangle strips:
// 0---2
// | / |
// 1---3
vertices[0].x = 0.0f;
vertices[0].y = 0.0f;
vertices[1].x = 0.0f;
vertices[1].y = (float) height;
vertices[2].x = (float) width;
vertices[2].y = 0.0f;
vertices[3].x = (float) width;
vertices[3].y = (float) height;
width = (int) (pixelWidth / settings.pixeldensity + 0.5);
height = (int) (pixelHeight / settings.pixeldensity + 0.5);
vertices[0].s = 0.0f;
vertices[0].t = 0.0f;
vertices[1].s = 0.0f;
vertices[1].t = 1.0f;
vertices[2].s = 1.0f;
vertices[2].t = 0.0f;
vertices[3].s = 1.0f;
vertices[3].t = 1.0f;
mipmapCount = mipmapsType == MIPMAPS_NONE ? 1 : getMipmapCount(w, h);
format = fmt;
compressed = isPixelFormatCompressed(format);
if (settings.mipmaps)
if (compressed && mipmapsType == MIPMAPS_GENERATED)
mipmapsType = MIPMAPS_NONE;
if (getMipmapCount() > 1)
filter.mipmap = defaultMipmapFilter;
if (!isGammaCorrect())
settings.linear = false;
if (isGammaCorrect() && !settings.linear)
sRGB = true;
else
sRGB = false;
loadVolatile();
initVertices();
}
void Image::generateMipmaps()
{
// The GL_GENERATE_MIPMAP texparameter is set in loadVolatile if we don't
// have support for glGenerateMipmap.
if (settings.mipmaps && !isCompressed() &&
(GLAD_ES_VERSION_2_0 || GLAD_VERSION_3_0 || GLAD_ARB_framebuffer_object))
if (getMipmapCount() > 1 && !isCompressed() &&
(GLAD_ES_VERSION_2_0 || GLAD_VERSION_3_0 || GLAD_ARB_framebuffer_object || GLAD_EXT_framebuffer_object))
{
if (gl.bugs.generateMipmapsRequiresTexture2DEnable)
glEnable(GL_TEXTURE_2D);
GLenum gltextype = OpenGL::getGLTextureType(texType);
glGenerateMipmap(GL_TEXTURE_2D);
if (gl.bugs.generateMipmapsRequiresTexture2DEnable)
glEnable(gltextype);
glGenerateMipmap(gltextype);
}
}
@@ -217,65 +116,120 @@ void Image::loadDefaultTexture()
{
usingDefaultTexture = true;
gl.bindTextureToUnit(texture, 0, false);
gl.bindTextureToUnit(this, 0, false);
setFilter(filter);
bool isSRGB = false;
gl.rawTexStorage(texType, 1, PIXELFORMAT_RGBA8, isSRGB, 2, 2, 1);
// A nice friendly checkerboard to signify invalid textures...
GLubyte px[] = {0xFF,0xFF,0xFF,0xFF, 0xFF,0xA0,0xA0,0xFF,
0xFF,0xA0,0xA0,0xFF, 0xFF,0xFF,0xFF,0xFF};
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, px);
int slices = texType == TEXTURE_CUBE ? 6 : 1;
Rect rect = {0, 0, 2, 2};
for (int slice = 0; slice < slices; slice++)
uploadByteData(PIXELFORMAT_RGBA8, px, sizeof(px), rect, 0, slice);
}
void Image::loadFromCompressedData()
void Image::loadData()
{
OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(format, false, sRGB);
int mipcount = getMipmapCount();
int slicecount = 1;
if (isGammaCorrect() && !sRGB)
settings.linear = true;
if (texType == TEXTURE_VOLUME)
slicecount = getDepth();
else if (texType == TEXTURE_2D_ARRAY)
slicecount = getLayerCount();
else if (texType == TEXTURE_CUBE)
slicecount = 6;
int count = 1;
if (!isCompressed())
gl.rawTexStorage(texType, mipcount, format, sRGB, pixelWidth, pixelHeight, texType == TEXTURE_VOLUME ? depth : layers);
if (settings.mipmaps && cdata.size() > 1)
count = (int) cdata.size();
else if (settings.mipmaps)
count = cdata[0]->getMipmapCount();
if (mipmapsType == MIPMAPS_GENERATED)
mipcount = 1;
for (int i = 0; i < count; i++)
int w = pixelWidth;
int h = pixelHeight;
int d = depth;
OpenGL::TextureFormat fmt = gl.convertPixelFormat(format, false, sRGB);
for (int mip = 0; mip < mipcount; mip++)
{
// Compressed image mipmaps can come from separate CompressedImageData
// objects, or all from a single object.
auto cd = cdata.size() > 1 ? cdata[i].get() : cdata[0].get();
int datamip = cdata.size() > 1 ? 0 : i;
if (isCompressed() && (texType == TEXTURE_2D_ARRAY || texType == TEXTURE_VOLUME))
{
size_t mipsize = 0;
glCompressedTexImage2D(GL_TEXTURE_2D, i, fmt.internalformat,
cd->getWidth(datamip), cd->getHeight(datamip), 0,
(GLsizei) cd->getSize(datamip), cd->getData(datamip));
}
}
if (texType == TEXTURE_2D_ARRAY || texType == TEXTURE_VOLUME)
{
for (int slice = 0; slice < data.getSliceCount(mip); slice++)
mipsize += data.get(slice, mip)->getSize();
}
void Image::loadFromImageData()
{
OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(format, false, sRGB);
GLenum gltarget = OpenGL::getGLTextureType(texType);
glCompressedTexImage3D(gltarget, mip, fmt.internalformat, w, h, d, 0, mipsize, nullptr);
}
if (isGammaCorrect() && !sRGB)
settings.linear = true;
for (int slice = 0; slice < slicecount; slice++)
{
love::image::ImageDataBase *id = data.get(slice, mip);
int mipcount = settings.mipmaps ? (int) data.size() : 1;
if (id != nullptr)
uploadImageData(id, mip, slice);
}
for (int i = 0; i < mipcount; i++)
{
love::image::ImageData *id = data[i].get();
love::thread::Lock lock(id->getMutex());
w = std::max(w / 2, 1);
h = std::max(h / 2, 1);
glTexImage2D(GL_TEXTURE_2D, i, fmt.internalformat, id->getWidth(), id->getHeight(),
0, fmt.externalformat, fmt.type, id->getData());
if (texType == TEXTURE_VOLUME)
d = std::max(d / 2, 1);
}
if (data.size() <= 1)
if (mipmapsType == MIPMAPS_GENERATED)
generateMipmaps();
}
void Image::uploadByteData(PixelFormat pixelformat, const void *data, size_t size, const Rect &r, int level, int slice)
{
OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(pixelformat, false, sRGB);
GLenum gltarget = OpenGL::getGLTextureType(texType);
if (texType == TEXTURE_CUBE)
gltarget = GL_TEXTURE_CUBE_MAP_POSITIVE_X + slice;
if (isPixelFormatCompressed(pixelformat))
{
if (r.x != 0 || r.y != 0)
throw love::Exception("x and y parameters must be 0 for compressed images.");
if (texType == TEXTURE_2D || texType == TEXTURE_CUBE)
glCompressedTexImage2D(gltarget, level, fmt.internalformat, r.w, r.h, 0, size, data);
else if (texType == TEXTURE_2D_ARRAY || texType == TEXTURE_VOLUME)
glCompressedTexSubImage3D(gltarget, level, 0, 0, slice, r.w, r.h, 1, fmt.internalformat, size, data);
}
else
{
if (texType == TEXTURE_2D || texType == TEXTURE_CUBE)
glTexSubImage2D(gltarget, level, r.x, r.y, r.w, r.h, fmt.externalformat, fmt.type, data);
else if (texType == TEXTURE_2D_ARRAY || texType == TEXTURE_VOLUME)
glTexSubImage3D(gltarget, level, r.x, r.y, slice, r.w, r.h, 1, fmt.externalformat, fmt.type, data);
}
}
void Image::uploadImageData(love::image::ImageDataBase *d, int level, int slice)
{
love::image::ImageData *id = dynamic_cast<love::image::ImageData *>(d);
love::thread::EmptyLock lock;
if (id != nullptr)
lock.setLock(id->getMutex());
Rect rect = {0, 0, d->getWidth(), d->getHeight()};
uploadByteData(d->getFormat(), d->getData(), d->getSize(), rect, level, slice);
}
bool Image::loadVolatile()
{
if (texture != 0)
@@ -301,9 +255,9 @@ bool Image::loadVolatile()
// GL_EXT_sRGB doesn't support glGenerateMipmap for sRGB textures.
if (sRGB && (GLAD_ES_VERSION_2_0 && GLAD_EXT_sRGB && !GLAD_ES_VERSION_3_0)
&& data.size() <= 1)
&& mipmapsType != MIPMAPS_DATA)
{
settings.mipmaps = false;
mipmapsType = MIPMAPS_NONE;
filter.mipmap = FILTER_NONE;
}
}
@@ -312,7 +266,7 @@ bool Image::loadVolatile()
if ((GLAD_ES_VERSION_2_0 && !(GLAD_ES_VERSION_3_0 || GLAD_OES_texture_npot))
&& (pixelWidth != nextP2(pixelWidth) || pixelHeight != nextP2(pixelHeight)))
{
settings.mipmaps = false;
mipmapsType = MIPMAPS_NONE;
filter.mipmap = FILTER_NONE;
}
@@ -320,38 +274,43 @@ bool Image::loadVolatile()
glGetFloatv(GL_MAX_TEXTURE_LOD_BIAS, &maxMipmapSharpness);
glGenTextures(1, &texture);
gl.bindTextureToUnit(texture, 0, false);
gl.bindTextureToUnit(this, 0, false);
setFilter(filter);
setWrap(wrap);
setMipmapSharpness(mipmapSharpness);
bool loaddefault = false;
int max2Dsize = gl.getMax2DTextureSize();
int max3Dsize = gl.getMax3DTextureSize();
if ((texType == TEXTURE_2D || texType == TEXTURE_2D_ARRAY) && (pixelWidth > max2Dsize || pixelHeight > max2Dsize))
loaddefault = true;
else if (texType == TEXTURE_2D_ARRAY && layers > gl.getMaxTextureLayers())
loaddefault = true;
else if (texType == TEXTURE_CUBE && (pixelWidth > gl.getMaxCubeTextureSize() || pixelWidth != pixelHeight))
loaddefault = true;
else if (texType == TEXTURE_VOLUME && (pixelWidth > max3Dsize || pixelHeight > max3Dsize || depth > max3Dsize))
loaddefault = true;
// Use a default texture if the size is too big for the system.
if (pixelWidth > gl.getMaxTextureSize() || pixelHeight > gl.getMaxTextureSize())
if (loaddefault)
{
loadDefaultTexture();
return true;
}
if (!settings.mipmaps && (GLAD_ES_VERSION_3_0 || GLAD_VERSION_1_0))
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
setFilter(filter);
setWrap(wrap);
setMipmapSharpness(mipmapSharpness);
if (settings.mipmaps && !isCompressed() && data.size() <= 1 &&
!(GLAD_ES_VERSION_2_0 || GLAD_VERSION_3_0 || GLAD_ARB_framebuffer_object))
{
// Auto-generate mipmaps every time the texture is modified, if
// glGenerateMipmap isn't supported.
glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
}
GLenum gltextype = OpenGL::getGLTextureType(texType);
if (mipmapsType == MIPMAPS_NONE && (GLAD_ES_VERSION_3_0 || GLAD_VERSION_1_0))
glTexParameteri(gltextype, GL_TEXTURE_MAX_LEVEL, 0);
while (glGetError() != GL_NO_ERROR); // Clear errors.
try
{
if (isCompressed())
loadFromCompressedData();
else
loadFromImageData();
loadData();
GLenum glerr = glGetError();
if (glerr != GL_NO_ERROR)
@@ -365,13 +324,12 @@ bool Image::loadVolatile()
}
size_t prevmemsize = textureMemorySize;
textureMemorySize = 0;
if (isCompressed())
textureMemorySize = cdata[0]->getSize();
else
textureMemorySize = data[0]->getSize();
for (int slice = 0; slice < data.getSliceCount(0); slice++)
textureMemorySize += data.get(slice, 0)->getSize();
if (settings.mipmaps)
if (getMipmapCount() > 1)
textureMemorySize *= 1.33334;
gl.updateTextureMemorySize(prevmemsize, textureMemorySize);
@@ -392,53 +350,61 @@ void Image::unloadVolatile()
textureMemorySize = 0;
}
bool Image::refresh(int xoffset, int yoffset, int w, int h)
void Image::replacePixels(love::image::ImageDataBase *d, int slice, int mipmap, bool reloadmipmaps)
{
// No effect if the texture hasn't been created yet.
if (texture == 0 || usingDefaultTexture)
return false;
return;
if (xoffset < 0 || yoffset < 0 || w <= 0 || h <= 0 ||
(xoffset + w) > pixelWidth || (yoffset + h) > pixelHeight)
if (d->getFormat() != getPixelFormat())
throw love::Exception("Pixel formats must match.");
if (mipmap < 0 || (mipmapsType != MIPMAPS_DATA && mipmap > 0) || mipmap >= getMipmapCount())
throw love::Exception("Invalid image mipmap index.");
if (slice < 0 || (texType == TEXTURE_CUBE && slice >= 6)
|| (texType == TEXTURE_VOLUME && slice >= std::max(getDepth() >> mipmap, 1))
|| (texType == TEXTURE_2D_ARRAY && slice >= getLayerCount()))
{
throw love::Exception("Invalid rectangle dimensions.");
throw love::Exception("Invalid image slice index.");
}
OpenGL::TempDebugGroup debuggroup("Image refresh");
love::image::ImageDataBase *oldd = data.get(slice, mipmap);
gl.bindTextureToUnit(texture, 0, false);
if (oldd == nullptr)
throw love::Exception("Image does not store ImageData!");
if (isCompressed())
{
loadFromCompressedData();
return true;
}
int w = d->getWidth();
int h = d->getHeight();
bool isSRGB = sRGB;
OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(format, false, isSRGB);
if (w != oldd->getWidth() || h != oldd->getHeight())
throw love::Exception("Dimensions must match the texture's dimensions for the specified mipmap level.");
int mipcount = settings.mipmaps ? (int) data.size() : 1;
d->retain();
oldd->release();
// Reupload the sub-rectangle of each mip level (if we have custom mipmaps.)
for (int i = 0; i < mipcount; i++)
{
const image::pixel *pdata = (const image::pixel *) data[i]->getData();
pdata += yoffset * data[i]->getWidth() + xoffset;
data.set(slice, mipmap, d);
thread::Lock lock(data[i]->getMutex());
glTexSubImage2D(GL_TEXTURE_2D, i, xoffset, yoffset, w, h,
fmt.externalformat, fmt.type, pdata);
OpenGL::TempDebugGroup debuggroup("Image replace pixels");
xoffset /= 2;
yoffset /= 2;
w = std::max(w / 2, 1);
h = std::max(h / 2, 1);
}
gl.bindTextureToUnit(this, 0, false);
if (data.size() <= 1)
uploadImageData(d, mipmap, slice);
if (reloadmipmaps && mipmap == 0 && getMipmapCount() > 1)
generateMipmaps();
}
return true;
void Image::replacePixels(const void *data, size_t size, const Rect &rect, int slice, int mipmap, bool reloadmipmaps)
{
OpenGL::TempDebugGroup debuggroup("Image replace pixels");
gl.bindTextureToUnit(this, 0, false);
uploadByteData(format, data, size, rect, mipmap, slice);
if (reloadmipmaps && mipmap == 0 && getMipmapCount() > 1)
generateMipmaps();
}
ptrdiff_t Image::getHandle() const
@@ -446,21 +412,11 @@ ptrdiff_t Image::getHandle() const
return texture;
}
const std::vector<StrongRef<love::image::ImageData>> &Image::getImageData() const
{
return data;
}
const std::vector<StrongRef<love::image::CompressedImageData>> &Image::getCompressedData() const
{
return cdata;
}
void Image::setFilter(const Texture::Filter &f)
{
if (!validateFilter(f, settings.mipmaps))
if (!validateFilter(f, getMipmapCount() > 1))
{
if (f.mipmap != FILTER_NONE && !settings.mipmaps)
if (f.mipmap != FILTER_NONE && getMipmapCount() == 1)
throw love::Exception("Non-mipmapped image cannot have mipmap filtering.");
else
throw love::Exception("Invalid texture filter.");
@@ -468,7 +424,7 @@ void Image::setFilter(const Texture::Filter &f)
filter = f;
if (!data.empty() && !OpenGL::hasTextureFilteringSupport(data[0]->getFormat()))
if (!OpenGL::hasTextureFilteringSupport(getPixelFormat()))
{
filter.mag = filter.min = FILTER_NEAREST;
@@ -483,57 +439,64 @@ void Image::setFilter(const Texture::Filter &f)
filter.min = filter.mag = FILTER_NEAREST;
}
gl.bindTextureToUnit(texture, 0, false);
gl.setTextureFilter(filter);
gl.bindTextureToUnit(this, 0, false);
gl.setTextureFilter(texType, filter);
}
bool Image::setWrap(const Texture::Wrap &w)
{
bool success = true;
bool forceclamp = texType == TEXTURE_CUBE;
wrap = w;
// If we only have limited NPOT support then the wrap mode must be CLAMP.
if ((GLAD_ES_VERSION_2_0 && !(GLAD_ES_VERSION_3_0 || GLAD_OES_texture_npot))
&& (pixelWidth != nextP2(pixelWidth) || pixelHeight != nextP2(pixelHeight)))
&& (pixelWidth != nextP2(pixelWidth) || pixelHeight != nextP2(pixelHeight) || depth != nextP2(depth)))
{
if (wrap.s != WRAP_CLAMP || wrap.t != WRAP_CLAMP)
forceclamp = true;
}
if (forceclamp)
{
if (wrap.s != WRAP_CLAMP || wrap.t != WRAP_CLAMP || wrap.r != WRAP_CLAMP)
success = false;
// If we only have limited NPOT support then the wrap mode must be CLAMP.
wrap.s = wrap.t = WRAP_CLAMP;
wrap.s = wrap.t = wrap.r = WRAP_CLAMP;
}
if (!gl.isClampZeroTextureWrapSupported())
{
if (wrap.s == WRAP_CLAMP_ZERO)
wrap.s = WRAP_CLAMP;
if (wrap.t == WRAP_CLAMP_ZERO)
wrap.t = WRAP_CLAMP;
if (wrap.s == WRAP_CLAMP_ZERO) wrap.s = WRAP_CLAMP;
if (wrap.t == WRAP_CLAMP_ZERO) wrap.t = WRAP_CLAMP;
if (wrap.r == WRAP_CLAMP_ZERO) wrap.r = WRAP_CLAMP;
}
gl.bindTextureToUnit(texture, 0, false);
gl.setTextureWrap(wrap);
gl.bindTextureToUnit(this, 0, false);
gl.setTextureWrap(texType, wrap);
return success;
}
void Image::setMipmapSharpness(float sharpness)
bool Image::setMipmapSharpness(float sharpness)
{
// OpenGL ES doesn't support LOD bias via glTexParameter.
if (!GLAD_VERSION_1_4)
return;
return false;
// LOD bias has the range (-maxbias, maxbias)
mipmapSharpness = std::min(std::max(sharpness, -maxMipmapSharpness + 0.01f), maxMipmapSharpness - 0.01f);
gl.bindTextureToUnit(texture, 0, false);
gl.bindTextureToUnit(this, 0, false);
// negative bias is sharper
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, -mipmapSharpness);
GLenum gltextype = OpenGL::getGLTextureType(texType);
glTexParameterf(gltextype, GL_TEXTURE_LOD_BIAS, -mipmapSharpness);
return true;
}
float Image::getMipmapSharpness() const
bool Image::isFormatLinear() const
{
return mipmapSharpness;
return isGammaCorrect() && !sRGB;
}
bool Image::isCompressed() const
@@ -541,6 +504,11 @@ bool Image::isCompressed() const
return compressed;
}
Image::MipmapsType Image::getMipmapsType() const
{
return mipmapsType;
}
bool Image::isFormatSupported(PixelFormat pixelformat)
{
return OpenGL::isPixelFormatSupported(pixelformat, false, false);
+13 -28
View File
@@ -38,8 +38,8 @@ class Image final : public love::graphics::Image, public Volatile
{
public:
Image(const std::vector<love::image::ImageData *> &data, const Settings &settings);
Image(const std::vector<love::image::CompressedImageData *> &cdata, const Settings &settings);
Image(const Slices &data, const Settings &settings);
Image(TextureType textype, PixelFormat format, int width, int height, int slices, const Settings &settings);
virtual ~Image();
@@ -49,52 +49,37 @@ public:
ptrdiff_t getHandle() const override;
const std::vector<StrongRef<love::image::ImageData>> &getImageData() const override;
const std::vector<StrongRef<love::image::CompressedImageData>> &getCompressedData() const override;
void setFilter(const Texture::Filter &f) override;
bool setWrap(const Texture::Wrap &w) override;
void setMipmapSharpness(float sharpness) override;
float getMipmapSharpness() const override;
bool setMipmapSharpness(float sharpness) override;
void replacePixels(love::image::ImageDataBase *d, int slice, int mipmap, bool reloadmipmaps) override;
void replacePixels(const void *data, size_t size, const Rect &rect, int slice, int mipmap, bool reloadmipmaps) override;
bool isFormatLinear() const override;
bool isCompressed() const override;
bool refresh(int xoffset, int yoffset, int w, int h) override;
MipmapsType getMipmapsType() const override;
static bool isFormatSupported(PixelFormat pixelformat);
static bool hasSRGBSupport();
static bool getConstant(const char *in, SettingType &out);
static bool getConstant(SettingType in, const char *&out);
private:
void preload();
void init(PixelFormat fmt, int w, int h, const Settings &settings);
void generateMipmaps();
void loadDefaultTexture();
void loadFromCompressedData();
void loadFromImageData();
// The ImageData from which the texture is created. May be empty if
// Compressed image data was used to create the texture.
// Each element in the array is a mipmap level.
std::vector<StrongRef<love::image::ImageData>> data;
// Or the Compressed Image Data from which the texture is created. May be
// empty if raw ImageData was used to create the texture.
std::vector<StrongRef<love::image::CompressedImageData>> cdata;
void loadData();
void uploadByteData(PixelFormat pixelformat, const void *data, size_t size, const Rect &rect, int level, int slice);
void uploadImageData(love::image::ImageDataBase *d, int level, int slice);
// OpenGL texture identifier.
GLuint texture;
// Mipmap texture LOD bias (sharpness) value.
float mipmapSharpness;
// Whether this Image is using a compressed texture.
bool compressed;
bool sRGB;
// True if the image wasn't able to be properly created and it had to fall
// back to a default texture.
bool usingDefaultTexture;
+7 -1
View File
@@ -99,6 +99,9 @@ void Mesh::drawInstanced(love::graphics::Graphics *gfx, const love::Matrix4 &m,
if (instancecount > 1 && !gl.isInstancingSupported())
throw love::Exception("Instancing is not supported on this system.");
if (Shader::current && texture.get())
Shader::current->checkMainTextureType(texture->getTextureType());
gfx->flushStreamDraws();
OpenGL::TempDebugGroup debuggroup("Mesh draw");
@@ -131,7 +134,10 @@ void Mesh::drawInstanced(love::graphics::Graphics *gfx, const love::Matrix4 &m,
gl.useVertexAttribArrays(enabledattribs, instancedattribs);
gl.bindTextureToUnit(texture, 0, false);
if (texture.get())
gl.bindTextureToUnit(texture, 0, false);
else
gl.bindTextureToUnit(TEXTURE_2D, gl.getDefaultTexture(TEXTURE_2D), 0, false);
Graphics::TempTransform transform(gfx, m);
+273 -42
View File
@@ -95,7 +95,10 @@ OpenGL::OpenGL()
, contextInitialized(false)
, pixelShaderHighpSupported(false)
, maxAnisotropy(1.0f)
, maxTextureSize(0)
, max2DTextureSize(0)
, max3DTextureSize(0)
, maxCubeTextureSize(0)
, maxTextureArrayLayers(0)
, maxRenderTargets(1)
, maxRenderbufferSamples(0)
, maxTextureUnits(1)
@@ -202,13 +205,18 @@ void OpenGL::setupContext()
}
// Initialize multiple texture unit support for shaders.
state.boundTextures.clear();
state.boundTextures.resize(maxTextureUnits, 0);
for (int i = 0; i < TEXTURE_MAX_ENUM; i++)
{
state.boundTextures[i].clear();
state.boundTextures[i].resize(maxTextureUnits, 0);
}
for (int i = 0; i < (int) state.boundTextures.size(); i++)
for (int i = 0; i < maxTextureUnits; i++)
{
glActiveTexture(GL_TEXTURE0 + i);
glBindTexture(GL_TEXTURE_2D, 0);
for (int j = 0; j < TEXTURE_MAX_ENUM; j++)
glBindTexture(getGLTextureType((TextureType) j), 0);
}
glActiveTexture(GL_TEXTURE0);
@@ -224,8 +232,14 @@ void OpenGL::deInitContext()
if (!contextInitialized)
return;
glDeleteTextures(1, &state.defaultTexture);
state.defaultTexture = 0;
for (int i = 0; i < TEXTURE_MAX_ENUM; i++)
{
if (state.defaultTexture[i] != 0)
{
gl.deleteTexture(state.defaultTexture[i]);
state.defaultTexture[i] = 0;
}
}
contextInitialized = false;
}
@@ -285,11 +299,15 @@ void OpenGL::initOpenGLFunctions()
fp_glGenFramebuffers = fp_glGenFramebuffersEXT;
fp_glCheckFramebufferStatus = fp_glCheckFramebufferStatusEXT;
fp_glFramebufferTexture2D = fp_glFramebufferTexture2DEXT;
fp_glFramebufferTexture3D = fp_glFramebufferTexture3DEXT;
fp_glFramebufferRenderbuffer = fp_glFramebufferRenderbufferEXT;
fp_glGetFramebufferAttachmentParameteriv = fp_glGetFramebufferAttachmentParameterivEXT;
fp_glGenerateMipmap = fp_glGenerateMipmapEXT;
}
if (GLAD_VERSION_1_0 && GLAD_EXT_texture_array)
fp_glFramebufferTextureLayer = fp_glFramebufferTextureLayerEXT;
if (GLAD_EXT_framebuffer_blit)
fp_glBlitFramebuffer = fp_glBlitFramebufferEXT;
else if (GLAD_ANGLE_framebuffer_blit)
@@ -328,6 +346,17 @@ void OpenGL::initOpenGLFunctions()
fp_glVertexAttribDivisor = fp_glVertexAttribDivisorANGLE;
}
}
if (GLAD_ES_VERSION_2_0 && GLAD_OES_texture_3D && !GLAD_ES_VERSION_3_0)
{
// Function signatures don't match, we'll have to conditionally call it
//fp_glTexImage3D = fp_glTexImage3DOES;
fp_glTexSubImage3D = fp_glTexSubImage3DOES;
fp_glCopyTexSubImage3D = fp_glCopyTexSubImage3DOES;
fp_glCompressedTexImage3D = fp_glCompressedTexImage3DOES;
fp_glCompressedTexSubImage3D = fp_glCompressedTexSubImage3DOES;
fp_glFramebufferTexture3D = fp_glFramebufferTexture3DOES;
}
}
void OpenGL::initMaxValues()
@@ -348,7 +377,18 @@ void OpenGL::initMaxValues()
else
maxAnisotropy = 1.0f;
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize);
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max2DTextureSize);
glGetIntegerv(GL_MAX_CUBE_MAP_TEXTURE_SIZE, &maxCubeTextureSize);
if (isTextureTypeSupported(TEXTURE_VOLUME))
glGetIntegerv(GL_MAX_3D_TEXTURE_SIZE, &max3DTextureSize);
else
max3DTextureSize = 0;
if (isTextureTypeSupported(TEXTURE_2D_ARRAY))
glGetIntegerv(GL_MAX_ARRAY_TEXTURE_LAYERS, &maxTextureArrayLayers);
else
maxTextureArrayLayers = 0;
int maxattachments = 1;
int maxdrawbuffers = 1;
@@ -382,26 +422,57 @@ void OpenGL::initMaxValues()
void OpenGL::createDefaultTexture()
{
// Set the 'default' texture (id 0) as a repeating white pixel. Otherwise,
// texture2D calls inside a shader would return black when drawing graphics
// primitives, which would create the need to use different "passthrough"
// shaders for untextured primitives vs images.
// Set the 'default' texture as a repeating white pixel. Otherwise, texture
// calls inside a shader would return black when drawing graphics primitives
// which would create the need to use different "passthrough" shaders for
// untextured primitives vs images.
const GLubyte pix[] = {255, 255, 255, 255};
GLuint curtexture = state.boundTextures[state.curTextureUnit];
Texture::Filter filter;
filter.min = filter.mag = Texture::FILTER_NEAREST;
glGenTextures(1, &state.defaultTexture);
bindTextureToUnit(state.defaultTexture, 0, false);
Texture::Wrap wrap;
wrap.s = wrap.t = wrap.r = Texture::WRAP_CLAMP;
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
for (int i = 0; i < TEXTURE_MAX_ENUM; i++)
{
state.defaultTexture[i] = 0;
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
TextureType type = (TextureType) i;
GLubyte pix[] = {255, 255, 255, 255};
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, pix);
if (!isTextureTypeSupported(type))
continue;
bindTextureToUnit(curtexture, 0, false);
GLuint curtexture = state.boundTextures[type][0];
glGenTextures(1, &state.defaultTexture[type]);
bindTextureToUnit(type, state.defaultTexture[type], 0, false);
setTextureWrap(type, wrap);
setTextureFilter(type, filter);
bool isSRGB = false;
rawTexStorage(type, 1, PIXELFORMAT_RGBA8, isSRGB, 1, 1);
TextureFormat fmt = convertPixelFormat(PIXELFORMAT_RGBA8, false, isSRGB);
int slices = type == TEXTURE_CUBE ? 6 : 1;
for (int slice = 0; slice < slices; slice++)
{
GLenum gltarget = getGLTextureType(type);
if (type == TEXTURE_CUBE)
gltarget = GL_TEXTURE_CUBE_MAP_POSITIVE_X + slice;
if (type == TEXTURE_2D || type == TEXTURE_CUBE)
glTexSubImage2D(gltarget, 0, 0, 0, 1, 1, fmt.externalformat, fmt.type, pix);
else if (type == TEXTURE_2D_ARRAY || type == TEXTURE_VOLUME)
glTexSubImage3D(gltarget, 0, 0, 0, slice, 1, 1, 1, fmt.externalformat, fmt.type, pix);
}
bindTextureToUnit(type, curtexture, 0, false);
}
}
void OpenGL::prepareDraw()
@@ -432,6 +503,27 @@ GLenum OpenGL::getGLBufferType(BufferType type)
case BUFFER_MAX_ENUM:
return GL_ZERO;
}
return GL_ZERO;
}
GLenum OpenGL::getGLTextureType(TextureType type)
{
switch (type)
{
case TEXTURE_2D:
return GL_TEXTURE_2D;
case TEXTURE_VOLUME:
return GL_TEXTURE_3D;
case TEXTURE_2D_ARRAY:
return GL_TEXTURE_2D_ARRAY;
case TEXTURE_CUBE:
return GL_TEXTURE_CUBE_MAP;
case TEXTURE_MAX_ENUM:
return GL_ZERO;
}
return GL_ZERO;
}
GLenum OpenGL::getGLIndexDataType(IndexDataType type)
@@ -651,6 +743,29 @@ void OpenGL::deleteFramebuffer(GLuint framebuffer)
}
}
void OpenGL::framebufferTexture(GLenum attachment, TextureType texType, GLuint texture, int level, int layer, int face)
{
GLenum textarget = getGLTextureType(texType);
switch (texType)
{
case TEXTURE_2D:
glFramebufferTexture2D(GL_FRAMEBUFFER, attachment, textarget, texture, level);
break;
case TEXTURE_VOLUME:
glFramebufferTexture3D(GL_FRAMEBUFFER, attachment, textarget, texture, level, layer);
break;
case TEXTURE_2D_ARRAY:
glFramebufferTextureLayer(GL_FRAMEBUFFER, attachment, texture, level, layer);
break;
case TEXTURE_CUBE:
glFramebufferTexture2D(GL_FRAMEBUFFER, attachment, GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, texture, level);
break;
default:
break;
}
}
void OpenGL::useProgram(GLuint program)
{
glUseProgram(program);
@@ -670,9 +785,9 @@ GLuint OpenGL::getDefaultFBO() const
#endif
}
GLuint OpenGL::getDefaultTexture() const
GLuint OpenGL::getDefaultTexture(TextureType type) const
{
return state.defaultTexture;
return state.defaultTexture[type];
}
void OpenGL::setTextureUnit(int textureunit)
@@ -683,16 +798,16 @@ void OpenGL::setTextureUnit(int textureunit)
state.curTextureUnit = textureunit;
}
void OpenGL::bindTextureToUnit(GLuint texture, int textureunit, bool restoreprev)
void OpenGL::bindTextureToUnit(TextureType target, GLuint texture, int textureunit, bool restoreprev)
{
if (texture != state.boundTextures[textureunit])
if (texture != state.boundTextures[target][textureunit])
{
int oldtextureunit = state.curTextureUnit;
if (oldtextureunit != textureunit)
glActiveTexture(GL_TEXTURE0 + textureunit);
state.boundTextures[textureunit] = texture;
glBindTexture(GL_TEXTURE_2D, texture);
state.boundTextures[target][textureunit] = texture;
glBindTexture(getGLTextureType(target), texture);
if (restoreprev && oldtextureunit != textureunit)
glActiveTexture(GL_TEXTURE0 + oldtextureunit);
@@ -703,24 +818,29 @@ void OpenGL::bindTextureToUnit(GLuint texture, int textureunit, bool restoreprev
void OpenGL::bindTextureToUnit(Texture *texture, int textureunit, bool restoreprev)
{
GLuint handle = texture != nullptr ? (GLuint) texture->getHandle() : getDefaultTexture();
bindTextureToUnit(handle, textureunit, restoreprev);
GLuint handle = texture != nullptr ? (GLuint) texture->getHandle() : getDefaultTexture(TEXTURE_2D);
TextureType textype = texture != nullptr ? texture->getTextureType() : TEXTURE_2D;
bindTextureToUnit(textype, handle, textureunit, restoreprev);
}
void OpenGL::deleteTexture(GLuint texture)
{
// glDeleteTextures binds texture 0 to all texture units the deleted texture
// was bound to before deletion.
for (GLuint &texid : state.boundTextures)
for (int i = 0; i < TEXTURE_MAX_ENUM; i++)
{
if (texid == texture)
texid = 0;
for (GLuint &texid : state.boundTextures[i])
{
if (texid == texture)
texid = 0;
}
}
glDeleteTextures(1, &texture);
}
void OpenGL::setTextureFilter(graphics::Texture::Filter &f)
void OpenGL::setTextureFilter(TextureType target, graphics::Texture::Filter &f)
{
GLint gmin, gmag;
@@ -756,13 +876,15 @@ void OpenGL::setTextureFilter(graphics::Texture::Filter &f)
break;
}
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gmin);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gmag);
GLenum gltarget = getGLTextureType(target);
glTexParameteri(gltarget, GL_TEXTURE_MIN_FILTER, gmin);
glTexParameteri(gltarget, GL_TEXTURE_MAG_FILTER, gmag);
if (GLAD_EXT_texture_filter_anisotropic)
{
f.anisotropy = std::min(std::max(f.anisotropy, 1.0f), maxAnisotropy);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, f.anisotropy);
glTexParameterf(gltarget, GL_TEXTURE_MAX_ANISOTROPY_EXT, f.anisotropy);
}
else
f.anisotropy = 1.0f;
@@ -785,10 +907,104 @@ GLint OpenGL::getGLWrapMode(Texture::WrapMode wmode)
}
void OpenGL::setTextureWrap(const graphics::Texture::Wrap &w)
void OpenGL::setTextureWrap(TextureType target, const graphics::Texture::Wrap &w)
{
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, getGLWrapMode(w.s));
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, getGLWrapMode(w.t));
glTexParameteri(getGLTextureType(target), GL_TEXTURE_WRAP_S, getGLWrapMode(w.s));
glTexParameteri(getGLTextureType(target), GL_TEXTURE_WRAP_T, getGLWrapMode(w.t));
if (target == TEXTURE_VOLUME)
glTexParameteri(getGLTextureType(target), GL_TEXTURE_WRAP_R, getGLWrapMode(w.r));
}
bool OpenGL::rawTexStorage(TextureType target, int levels, PixelFormat pixelformat, bool &isSRGB, int width, int height, int depth)
{
GLenum gltarget = getGLTextureType(target);
TextureFormat fmt = convertPixelFormat(pixelformat, false, isSRGB);
if (fmt.swizzled)
{
glTexParameteri(gltarget, GL_TEXTURE_SWIZZLE_R, fmt.swizzle[0]);
glTexParameteri(gltarget, GL_TEXTURE_SWIZZLE_G, fmt.swizzle[1]);
glTexParameteri(gltarget, GL_TEXTURE_SWIZZLE_B, fmt.swizzle[2]);
glTexParameteri(gltarget, GL_TEXTURE_SWIZZLE_A, fmt.swizzle[3]);
}
bool supportsTexStorage = GLAD_VERSION_4_2 || GLAD_ARB_texture_storage;
// Apparently there are bugs with glTexStorage on some Android drivers. I'd
// rather not find out the hard way, so we'll avoid it for now...
#ifndef LOVE_ANDROID
if (GLAD_ES_VERSION_3_0)
supportsTexStorage = true;
#endif
if (supportsTexStorage)
{
if (target == TEXTURE_2D || target == TEXTURE_CUBE)
glTexStorage2D(gltarget, levels, fmt.internalformat, width, height);
else if (target == TEXTURE_VOLUME || target == TEXTURE_2D_ARRAY)
glTexStorage3D(gltarget, levels, fmt.internalformat, width, height, depth);
}
else
{
int w = width;
int h = height;
int d = depth;
for (int level = 0; level < levels; level++)
{
if (target == TEXTURE_2D || target == TEXTURE_CUBE)
{
int faces = target == TEXTURE_CUBE ? 6 : 1;
for (int face = 0; face < faces; face++)
{
if (target == TEXTURE_CUBE)
gltarget = GL_TEXTURE_CUBE_MAP_POSITIVE_X + face;
glTexImage2D(gltarget, level, fmt.internalformat, w, h, 0,
fmt.externalformat, fmt.type, nullptr);
}
}
else if (target == TEXTURE_2D_ARRAY || target == TEXTURE_VOLUME)
{
if (target == TEXTURE_VOLUME && GLAD_ES_VERSION_2_0 && GLAD_OES_texture_3D && !GLAD_ES_VERSION_3_0)
{
glTexImage3DOES(gltarget, level, fmt.internalformat, w, h,
d, 0, fmt.externalformat, fmt.type, nullptr);
}
else
{
glTexImage3D(gltarget, level, fmt.internalformat, w, h, d,
0, fmt.externalformat, fmt.type, nullptr);
}
}
w = std::max(w / 2, 1);
h = std::max(h / 2, 1);
if (target == TEXTURE_VOLUME)
d = std::max(d / 2, 1);
}
}
return gltarget != GL_ZERO;
}
bool OpenGL::isTextureTypeSupported(TextureType type) const
{
switch (type)
{
case TEXTURE_2D:
return true;
case TEXTURE_VOLUME:
return GLAD_VERSION_1_1 || GLAD_ES_VERSION_3_0 || GLAD_OES_texture_3D;
case TEXTURE_2D_ARRAY:
return GLAD_VERSION_3_0 || GLAD_ES_VERSION_3_0 || GLAD_EXT_texture_array;
case TEXTURE_CUBE:
return GLAD_VERSION_1_3 || GLAD_ES_VERSION_2_0;
default:
return false;
}
}
bool OpenGL::isClampZeroTextureWrapSupported() const
@@ -807,9 +1023,24 @@ bool OpenGL::isInstancingSupported() const
|| GLAD_ARB_instanced_arrays || GLAD_EXT_instanced_arrays || GLAD_ANGLE_instanced_arrays;
}
int OpenGL::getMaxTextureSize() const
int OpenGL::getMax2DTextureSize() const
{
return maxTextureSize;
return std::max(max2DTextureSize, 1);
}
int OpenGL::getMax3DTextureSize() const
{
return std::max(max3DTextureSize, 1);
}
int OpenGL::getMaxCubeTextureSize() const
{
return std::max(maxCubeTextureSize, 1);
}
int OpenGL::getMaxTextureLayers() const
{
return std::max(maxTextureArrayLayers, 1);
}
int OpenGL::getMaxRenderTargets() const
+25 -8
View File
@@ -248,6 +248,8 @@ public:
GLuint getFramebuffer(FramebufferTarget target) const;
void deleteFramebuffer(GLuint framebuffer);
void framebufferTexture(GLenum attachment, TextureType texType, GLuint texture, int level, int layer = 0, int face = 0);
/**
* Calls glUseProgram.
**/
@@ -262,7 +264,7 @@ public:
/**
* Gets the ID for love's default texture (used for "untextured" primitives.)
**/
GLuint getDefaultTexture() const;
GLuint getDefaultTexture(TextureType type) const;
/**
* Helper for setting the active texture unit.
@@ -277,7 +279,7 @@ public:
* @param textureunit Index in the range of [0, maxtextureunits-1]
* @param restoreprev Restore previously bound texture unit when done.
**/
void bindTextureToUnit(GLuint texture, int textureunit, bool restoreprev);
void bindTextureToUnit(TextureType target, GLuint texture, int textureunit, bool restoreprev);
void bindTextureToUnit(Texture *texture, int textureunit, bool restoreprev);
/**
@@ -291,13 +293,21 @@ public:
* The anisotropy parameter of the argument is set to the actual amount of
* anisotropy that was used.
**/
void setTextureFilter(graphics::Texture::Filter &f);
void setTextureFilter(TextureType target, graphics::Texture::Filter &f);
/**
* Sets the texture wrap mode for the currently bound texture.
**/
void setTextureWrap(const graphics::Texture::Wrap &w);
void setTextureWrap(TextureType target, const graphics::Texture::Wrap &w);
/**
* Equivalent to glTexStorage2D/3D on platforms that support it. Equivalent
* to glTexImage2D/3D for all levels and slices of a texture otherwise.
* NOTE: this does not handle compressed texture formats.
**/
bool rawTexStorage(TextureType target, int levels, PixelFormat pixelformat, bool &isSRGB, int width, int height, int depth = 1);
bool isTextureTypeSupported(TextureType type) const;
bool isClampZeroTextureWrapSupported() const;
bool isPixelShaderHighpSupported() const;
bool isInstancingSupported() const;
@@ -305,7 +315,10 @@ public:
/**
* Returns the maximum supported width or height of a texture.
**/
int getMaxTextureSize() const;
int getMax2DTextureSize() const;
int getMax3DTextureSize() const;
int getMaxCubeTextureSize() const;
int getMaxTextureLayers() const;
/**
* Returns the maximum supported number of simultaneous render targets.
@@ -349,6 +362,7 @@ public:
static GLenum getGLBufferType(BufferType type);
static GLenum getGLIndexDataType(IndexDataType type);
static GLenum getGLBufferUsage(vertex::Usage usage);
static GLenum getGLTextureType(TextureType type);
static GLint getGLWrapMode(Texture::WrapMode wmode);
static TextureFormat convertPixelFormat(PixelFormat pixelformat, bool renderbuffer, bool &isSRGB);
@@ -374,7 +388,10 @@ private:
bool pixelShaderHighpSupported;
float maxAnisotropy;
int maxTextureSize;
int max2DTextureSize;
int max3DTextureSize;
int maxCubeTextureSize;
int maxTextureArrayLayers;
int maxRenderTargets;
int maxRenderbufferSamples;
int maxTextureUnits;
@@ -390,7 +407,7 @@ private:
GLuint boundBuffers[BUFFER_MAX_ENUM];
// Texture unit state (currently bound texture for each texture unit.)
std::vector<GLuint> boundTextures;
std::vector<GLuint> boundTextures[TEXTURE_MAX_ENUM];
// Currently active texture unit.
int curTextureUnit;
@@ -410,7 +427,7 @@ private:
bool framebufferSRGBEnabled;
GLuint defaultTexture;
GLuint defaultTexture[TEXTURE_MAX_ENUM];
} state;
@@ -55,6 +55,9 @@ void ParticleSystem::draw(Graphics *gfx, const Matrix4 &m)
if (!prepareDraw(gfx, m))
return;
if (Shader::current && texture.get())
Shader::current->checkMainTextureType(texture->getTextureType());
OpenGL::TempDebugGroup debuggroup("ParticleSystem draw");
gl.bindTextureToUnit(texture, 0, false);
+143 -140
View File
@@ -40,11 +40,11 @@ Shader::Shader(const ShaderSource &source)
: love::graphics::Shader(source)
, program(0)
, builtinUniforms()
, builtinUniformInfo()
, builtinAttributes()
, canvasWasActive(false)
, lastViewport()
, lastPointSize(0.0f)
, videoTextureUnits()
{
// load shader source and create program object
loadVolatile();
@@ -142,7 +142,10 @@ void Shader::mapActiveUniforms()
{
// Built-in uniform locations default to -1 (nonexistent.)
for (int i = 0; i < int(BUILTIN_MAX_ENUM); i++)
{
builtinUniforms[i] = -1;
builtinUniformInfo[i] = nullptr;
}
GLint activeprogram = 0;
glGetIntegerv(GL_CURRENT_PROGRAM, &activeprogram);
@@ -158,17 +161,18 @@ void Shader::mapActiveUniforms()
std::map<std::string, UniformInfo> olduniforms = uniforms;
uniforms.clear();
for (int i = 0; i < numuniforms; i++)
for (int uindex = 0; uindex < numuniforms; uindex++)
{
GLsizei namelen = 0;
GLenum gltype = 0;
UniformInfo u = {};
glGetActiveUniform(program, (GLuint) i, bufsize, &namelen, &u.count, &gltype, cname);
glGetActiveUniform(program, (GLuint) uindex, bufsize, &namelen, &u.count, &gltype, cname);
u.name = std::string(cname, (size_t) namelen);
u.location = glGetUniformLocation(program, u.name.c_str());
u.baseType = getUniformBaseType(gltype);
u.textureType = getUniformTextureType(gltype);
if (u.baseType == UNIFORM_MATRIX)
u.matrix = getMatrixSize(gltype);
@@ -184,13 +188,24 @@ void Shader::mapActiveUniforms()
}
// If this is a built-in (LOVE-created) uniform, store the location.
BuiltinUniform builtin;
BuiltinUniform builtin = BUILTIN_MAX_ENUM;
if (getConstant(u.name.c_str(), builtin))
builtinUniforms[int(builtin)] = u.location;
if (u.location == -1)
continue;
if (u.baseType == UNIFORM_SAMPLER && builtin != BUILTIN_TEXTURE_MAIN)
{
TextureUnit unit;
unit.type = u.textureType;
unit.active = true;
unit.texture = gl.getDefaultTexture(u.textureType);
for (int i = 0; i < u.count; i++)
textureUnits.push_back(unit);
}
// Make sure previously set uniform data is preserved, and shader-
// initialized values are retrieved.
auto oldu = olduniforms.find(u.name);
@@ -200,23 +215,6 @@ void Shader::mapActiveUniforms()
u.textures = oldu->second.textures;
updateUniform(&u, u.count, true);
if (u.baseType == UNIFORM_SAMPLER)
{
// Make sure all stored textures have their Volatiles loaded
// before the sendTextures call, since it calls getHandle().
for (int i = 0; i < u.count; i++)
{
if (u.textures[i] == nullptr)
continue;
Volatile *v = dynamic_cast<Volatile *>(u.textures[i]);
if (v != nullptr)
v->loadVolatile();
}
sendTextures(&u, u.textures, u.count, true);
}
}
else
{
@@ -252,9 +250,14 @@ void Shader::mapActiveUniforms()
if (u.baseType == UNIFORM_SAMPLER)
{
// Initialize all samplers to 0. Both GLSL and GLSL ES are
// supposed to do this themselves, but some Android devices
// (galaxy tab 3 and 4) don't seem to do it...
int startunit = (int) textureUnits.size() - u.count;
if (builtin == BUILTIN_TEXTURE_MAIN)
startunit = 0;
for (int i = 0; i < u.count; i++)
u.ints[i] = startunit + i;
glUniform1iv(u.location, u.count, u.ints);
u.textures = new Texture*[u.count];
@@ -307,6 +310,26 @@ void Shader::mapActiveUniforms()
}
uniforms[u.name] = u;
if (builtin != BUILTIN_MAX_ENUM)
builtinUniformInfo[(int)builtin] = &uniforms[u.name];
if (u.baseType == UNIFORM_SAMPLER)
{
// Make sure all stored textures have their Volatiles loaded before
// the sendTextures call, since it calls getHandle().
for (int i = 0; i < u.count; i++)
{
if (u.textures[i] == nullptr)
continue;
Volatile *v = dynamic_cast<Volatile *>(u.textures[i]);
if (v != nullptr)
v->loadVolatile();
}
sendTextures(&u, u.textures, u.count, true);
}
}
// Make sure uniforms that existed before but don't exist anymore are
@@ -348,12 +371,9 @@ bool Shader::loadVolatile()
lastProjectionMatrix.setTranslation(nan, nan);
lastTransformMatrix.setTranslation(nan, nan);
for (int i = 0; i < 3; i++)
videoTextureUnits[i] = 0;
// zero out active texture list
textureUnits.clear();
textureUnits.resize(gl.getMaxTextureUnits(), TextureUnit());
textureUnits.push_back(TextureUnit());
std::vector<GLuint> shaderids;
@@ -449,7 +469,7 @@ void Shader::unloadVolatile()
// active texture list is probably invalid, clear it
textureUnits.clear();
textureUnits.resize(gl.getMaxTextureUnits(), TextureUnit());
textureUnits.push_back(TextureUnit());
attributes.clear();
@@ -497,7 +517,7 @@ std::string Shader::getWarnings() const
return warnings;
}
void Shader::attach(bool temporary)
void Shader::attach()
{
if (current != this)
{
@@ -505,22 +525,19 @@ void Shader::attach(bool temporary)
current = this;
// retain/release happens in Graphics::setShader.
if (!temporary)
// Make sure all textures are bound to their respective texture units.
for (int i = 0; i < (int) textureUnits.size(); ++i)
{
// Make sure all textures are properly bound to their respective
// texture units.
for (int i = 1; i < (int) textureUnits.size(); ++i)
{
if (textureUnits[i].active)
gl.bindTextureToUnit(textureUnits[i].texture, i, false);
}
// send any pending uniforms to the shader program.
for (const auto &p : pendingUniformUpdates)
updateUniform(p.first, p.second);
pendingUniformUpdates.clear();
const TextureUnit &unit = textureUnits[i];
if (unit.active)
gl.bindTextureToUnit(unit.type, unit.texture, i, false);
}
// send any pending uniforms to the shader program.
for (const auto &p : pendingUniformUpdates)
updateUniform(p.first, p.second, true);
pendingUniformUpdates.clear();
}
}
@@ -534,15 +551,25 @@ const Shader::UniformInfo *Shader::getUniformInfo(const std::string &name) const
return &(it->second);
}
void Shader::updateUniform(const UniformInfo *info, int count, bool internalUpdate)
const Shader::UniformInfo *Shader::getUniformInfo(BuiltinUniform builtin) const
{
if (current != this)
return builtinUniformInfo[(int)builtin];
}
void Shader::updateUniform(const UniformInfo *info, int count)
{
updateUniform(info, count, false);
}
void Shader::updateUniform(const UniformInfo *info, int count, bool internalupdate)
{
if (current != this && !internalupdate)
{
pendingUniformUpdates.push_back(std::make_pair(info, count));
return;
}
if (!internalUpdate)
if (!internalupdate)
flushStreamDraws();
int location = info->location;
@@ -628,24 +655,9 @@ void Shader::updateUniform(const UniformInfo *info, int count, bool internalUpda
}
}
int Shader::getFreeTextureUnits(int count)
void Shader::sendTextures(const UniformInfo *info, Texture **textures, int count)
{
int startunit = -1;
// Ignore the first texture unit for Shader-local texture bindings.
for (int i = 1; i < (int) textureUnits.size(); i++)
{
if (!textureUnits[i].active && i + count <= (int) textureUnits.size())
{
startunit = i;
break;
}
}
if (startunit == -1)
throw love::Exception("No more texture units available for shader.");
return startunit;
Shader::sendTextures(info, textures, count, false);
}
void Shader::sendTextures(const UniformInfo *info, Texture **textures, int count, bool internalUpdate)
@@ -659,55 +671,36 @@ void Shader::sendTextures(const UniformInfo *info, Texture **textures, int count
flushStreamDraws();
count = std::min(count, info->count);
bool updateuniform = false;
// Make sure the shader's samplers are associated with texture units.
for (int i = 0; i < count; i++)
{
if (info->ints[i] == 0 && textures[i] != nullptr)
{
int texunit = getFreeTextureUnits(1);
textureUnits[texunit].active = true;
info->ints[i] = texunit;
updateuniform = true;
}
}
if (updateuniform)
updateUniform(info, count, internalUpdate);
// Bind the textures to the texture units.
for (int i = 0; i < count; i++)
{
if (textures[i] != nullptr)
{
if (textures[i]->getTextureType() != info->textureType)
continue;
textures[i]->retain();
}
if (info->textures[i] != nullptr)
info->textures[i]->release();
info->textures[i] = textures[i];
GLuint gltex = 0;
if (textures[i] != nullptr)
gltex = (GLuint) textures[i]->getHandle();
else
gltex = gl.getDefaultTexture(info->textureType);
int texunit = info->ints[i];
if (textures[i] != nullptr)
{
GLuint gltex = (GLuint) textures[i]->getHandle();
if (shaderactive)
gl.bindTextureToUnit(info->textureType, gltex, texunit, false);
if (shaderactive)
gl.bindTextureToUnit(gltex, texunit, false);
// Store texture id so it can be re-bound to the texture unit later.
textureUnits[texunit].texture = gltex;
}
else
{
if (shaderactive)
gl.bindTextureToUnit((GLuint) 0, texunit, false);
textureUnits[texunit].texture = 0;
textureUnits[texunit].active = false;
}
// Store texture id so it can be re-bound to the texture unit later.
textureUnits[texunit].texture = gltex;
}
}
@@ -743,50 +736,22 @@ GLint Shader::getAttribLocation(const std::string &name)
return location;
}
void Shader::setVideoTextures(ptrdiff_t ytexture, ptrdiff_t cbtexture, ptrdiff_t crtexture)
void Shader::setVideoTextures(Texture *ytexture, Texture *cbtexture, Texture *crtexture)
{
// Set up the texture units that will be used by the shader to sample from
// the textures, if they haven't been set up yet.
if (videoTextureUnits[0] == 0)
{
const BuiltinUniform builtins[3] = {
BUILTIN_TEXTURE_VIDEO_Y,
BUILTIN_TEXTURE_VIDEO_CB,
BUILTIN_TEXTURE_VIDEO_CR,
};
const BuiltinUniform builtins[3] = {
BUILTIN_TEXTURE_VIDEO_Y,
BUILTIN_TEXTURE_VIDEO_CB,
BUILTIN_TEXTURE_VIDEO_CR,
};
for (int i = 0; i < 3; i++)
{
GLint loc = builtinUniforms[builtins[i]];
const char *name = nullptr;;
Texture *textures[3] = {ytexture, cbtexture, crtexture};
if (loc >= 0 && getConstant(builtins[i], name) && name != nullptr)
{
const UniformInfo *info = getUniformInfo(name);
if (info == nullptr)
continue;
videoTextureUnits[i] = getFreeTextureUnits(1);
textureUnits[videoTextureUnits[i]].active = true;
info->ints[0] = videoTextureUnits[i];
updateUniform(info, 1);
}
}
}
const GLuint textures[3] = {(GLuint) ytexture, (GLuint) cbtexture, (GLuint) crtexture};
// Bind the textures to their respective texture units.
for (int i = 0; i < 3; i++)
{
if (videoTextureUnits[i] != 0)
{
// Store texture id so it can be re-bound later.
textureUnits[videoTextureUnits[i]].texture = textures[i];
if (current == this)
gl.bindTextureToUnit(textures[i], videoTextureUnits[i], false);
}
const UniformInfo *info = builtinUniformInfo[builtins[i]];
if (info != nullptr)
sendTextures(info, &textures[i], 1);
}
}
@@ -925,15 +890,15 @@ bool Shader::isSupported()
int Shader::getUniformTypeComponents(GLenum type) const
{
if (getUniformBaseType(type) == UNIFORM_SAMPLER)
return 1;
switch (type)
{
case GL_INT:
case GL_UNSIGNED_INT:
case GL_FLOAT:
case GL_BOOL:
case GL_SAMPLER_1D:
case GL_SAMPLER_2D:
case GL_SAMPLER_3D:
return 1;
case GL_INT_VEC2:
case GL_UNSIGNED_INT_VEC2:
@@ -1059,6 +1024,44 @@ Shader::UniformType Shader::getUniformBaseType(GLenum type) const
}
}
TextureType Shader::getUniformTextureType(GLenum type) const
{
switch (type)
{
case GL_SAMPLER_1D:
case GL_SAMPLER_1D_SHADOW:
case GL_SAMPLER_1D_ARRAY:
case GL_SAMPLER_1D_ARRAY_SHADOW:
// 1D-typed textures are not supported.
return TEXTURE_MAX_ENUM;
case GL_SAMPLER_2D:
//case GL_SAMPLER_2D_SHADOW:
return TEXTURE_2D;
case GL_SAMPLER_2D_MULTISAMPLE:
case GL_SAMPLER_2D_MULTISAMPLE_ARRAY:
// Multisample textures are not supported.
return TEXTURE_MAX_ENUM;
case GL_SAMPLER_2D_RECT:
case GL_SAMPLER_2D_RECT_SHADOW:
// Rectangle textures are not supported.
return TEXTURE_MAX_ENUM;
case GL_SAMPLER_2D_ARRAY:
//case GL_SAMPLER_2D_ARRAY_SHADOW:
return TEXTURE_2D_ARRAY;
case GL_SAMPLER_3D:
return TEXTURE_VOLUME;
case GL_SAMPLER_CUBE:
//case GL_SAMPLER_CUBE_SHADOW:
return TEXTURE_CUBE;
case GL_SAMPLER_CUBE_MAP_ARRAY:
case GL_SAMPLER_CUBE_MAP_ARRAY_SHADOW:
// Cubemap array textures are not supported.
return TEXTURE_MAX_ENUM;
default:
return TEXTURE_MAX_ENUM;
}
}
} // opengl
} // graphics
} // love
+11 -8
View File
@@ -56,14 +56,15 @@ public:
void unloadVolatile() override;
// Implements Shader.
void attach(bool temporary = false) override;
void attach() override;
std::string getWarnings() const override;
const UniformInfo *getUniformInfo(const std::string &name) const override;
void updateUniform(const UniformInfo *info, int count, bool internalUpdate = false) override;
void sendTextures(const UniformInfo *info, Texture **textures, int count, bool internalUpdate = false) override;
const UniformInfo *getUniformInfo(BuiltinUniform builtin) const override;
void updateUniform(const UniformInfo *info, int count) override;
void sendTextures(const UniformInfo *info, Texture **textures, int count) override;
bool hasUniform(const std::string &name) const override;
ptrdiff_t getHandle() const override;
void setVideoTextures(ptrdiff_t ytexture, ptrdiff_t cbtexture, ptrdiff_t crtexture) override;
void setVideoTextures(Texture *ytexture, Texture *cbtexture, Texture *crtexture) override;
GLint getAttribLocation(const std::string &name);
@@ -79,20 +80,23 @@ private:
struct TextureUnit
{
GLuint texture = 0;
TextureType type = TEXTURE_2D;
bool active = false;
};
// Map active uniform names to their locations.
void mapActiveUniforms();
void updateUniform(const UniformInfo *info, int count, bool internalupdate);
void sendTextures(const UniformInfo *info, Texture **textures, int count, bool internalupdate);
int getUniformTypeComponents(GLenum type) const;
MatrixSize getMatrixSize(GLenum type) const;
UniformType getUniformBaseType(GLenum type) const;
TextureType getUniformTextureType(GLenum type) const;
GLuint compileCode(ShaderStage stage, const std::string &code);
int getFreeTextureUnits(int count);
void flushStreamDraws() const;
// Get any warnings or errors generated only by the shader program object.
@@ -106,6 +110,7 @@ private:
// Location values for any built-in uniform variables.
GLint builtinUniforms[BUILTIN_MAX_ENUM];
UniformInfo *builtinUniformInfo[BUILTIN_MAX_ENUM];
// Location values for any generic vertex attribute variables.
GLint builtinAttributes[ATTRIB_MAX_ENUM];
@@ -128,8 +133,6 @@ private:
Matrix4 lastTransformMatrix;
Matrix4 lastProjectionMatrix;
GLuint videoTextureUnits[3];
}; // Shader
} // opengl
+9 -10
View File
@@ -53,38 +53,37 @@ SpriteBatch::~SpriteBatch()
void SpriteBatch::draw(Graphics *gfx, const Matrix4 &m)
{
const size_t pos_offset = offsetof(Vertex, x);
const size_t texel_offset = offsetof(Vertex, s);
const size_t color_offset = offsetof(Vertex, color.r);
if (next == 0)
return;
gfx->flushStreamDraws();
if (Shader::current && texture.get())
Shader::current->checkMainTextureType(texture->getTextureType());
OpenGL::TempDebugGroup debuggroup("SpriteBatch draw");
Graphics::TempTransform transform(gfx, m);
gl.bindTextureToUnit(texture, 0, false);
uint32 enabledattribs = ATTRIBFLAG_POS | ATTRIBFLAG_TEXCOORD;
// Make sure the VBO isn't mapped when we draw (sends data to GPU if needed.)
array_buf->unmap();
gl.bindBuffer(BUFFER_VERTEX, (GLuint) array_buf->getHandle());
uint32 enabledattribs = ATTRIBFLAG_POS | ATTRIBFLAG_TEXCOORD;
glVertexAttribPointer(ATTRIB_POS, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), BUFFER_OFFSET(offsetof(Vertex, x)));
glVertexAttribPointer(ATTRIB_TEXCOORD, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), BUFFER_OFFSET(offsetof(Vertex, s)));
// Apply per-sprite color, if a color is set.
if (color)
{
enabledattribs |= ATTRIBFLAG_COLOR;
glVertexAttribPointer(ATTRIB_COLOR, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(Vertex), BUFFER_OFFSET(color_offset));
glVertexAttribPointer(ATTRIB_COLOR, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(Vertex), BUFFER_OFFSET(offsetof(Vertex, color.r)));
}
glVertexAttribPointer(ATTRIB_POS, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), BUFFER_OFFSET(pos_offset));
glVertexAttribPointer(ATTRIB_TEXCOORD, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), BUFFER_OFFSET(texel_offset));
for (const auto &it : attached_attributes)
{
Mesh *mesh = it.second.mesh.get();
+12 -13
View File
@@ -45,6 +45,9 @@ void Text::draw(Graphics *gfx, const Matrix4 &m)
if (vbo == nullptr || draw_commands.empty())
return;
if (Shader::current)
Shader::current->checkMainTextureType(TEXTURE_2D);
gfx->flushStreamDraws();
OpenGL::TempDebugGroup debuggroup("Text object draw");
@@ -60,28 +63,24 @@ void Text::draw(Graphics *gfx, const Matrix4 &m)
if ((size_t) totalverts / 4 > quadIndices.getSize())
quadIndices = QuadIndices(gfx, (size_t) totalverts / 4);
const size_t pos_offset = offsetof(Font::GlyphVertex, x);
const size_t tex_offset = offsetof(Font::GlyphVertex, s);
const size_t color_offset = offsetof(Font::GlyphVertex, color.r);
const size_t stride = sizeof(Font::GlyphVertex);
const GLenum gltype = OpenGL::getGLIndexDataType(quadIndices.getType());
const size_t elemsize = quadIndices.getElementSize();
vbo->unmap(); // Make sure all pending data is flushed to the GPU.
Graphics::TempTransform transform(gfx, m);
gl.prepareDraw();
vbo->unmap(); // Make sure all pending data is flushed to the GPU.
size_t stride = sizeof(Font::GlyphVertex);
gl.bindBuffer(BUFFER_VERTEX, (GLuint) vbo->getHandle());
glVertexAttribPointer(ATTRIB_POS, 2, GL_FLOAT, GL_FALSE, stride, BUFFER_OFFSET(pos_offset));
glVertexAttribPointer(ATTRIB_TEXCOORD, 2, GL_UNSIGNED_SHORT, GL_TRUE, stride, BUFFER_OFFSET(tex_offset));
glVertexAttribPointer(ATTRIB_COLOR, 4, GL_UNSIGNED_BYTE, GL_TRUE, stride, BUFFER_OFFSET(color_offset));
glVertexAttribPointer(ATTRIB_POS, 2, GL_FLOAT, GL_FALSE, stride, BUFFER_OFFSET(offsetof(Font::GlyphVertex, x)));
glVertexAttribPointer(ATTRIB_TEXCOORD, 2, GL_UNSIGNED_SHORT, GL_TRUE, stride, BUFFER_OFFSET(offsetof(Font::GlyphVertex, s)));
glVertexAttribPointer(ATTRIB_COLOR, 4, GL_UNSIGNED_BYTE, GL_TRUE, stride, BUFFER_OFFSET(offsetof(Font::GlyphVertex, color.r)));
gl.useVertexAttribArrays(ATTRIBFLAG_POS | ATTRIBFLAG_TEXCOORD | ATTRIBFLAG_COLOR);
const GLenum gltype = OpenGL::getGLIndexDataType(quadIndices.getType());
const size_t elemsize = quadIndices.getElementSize();
gl.bindBuffer(BUFFER_INDEX, (GLuint) quadIndices.getBuffer()->getHandle());
// We need a separate draw call for every section of the text which uses a
@@ -92,7 +91,7 @@ void Text::draw(Graphics *gfx, const Matrix4 &m)
size_t offset = (cmd.startvertex / 4) * 6 * elemsize;
// TODO: Use glDrawElementsBaseVertex when supported?
gl.bindTextureToUnit((GLuint) cmd.texture, 0, false);
gl.bindTextureToUnit(cmd.texture, 0, false);
gl.drawElements(GL_TRIANGLES, count, gltype, BUFFER_OFFSET(offset));
}
-119
View File
@@ -1,119 +0,0 @@
/**
* Copyright (c) 2006-2017 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
**/
#include "Video.h"
namespace love
{
namespace graphics
{
namespace opengl
{
Video::Video(love::video::VideoStream *stream, float pixeldensity)
: love::graphics::Video(stream, pixeldensity)
{
loadVolatile();
}
Video::~Video()
{
unloadVolatile();
}
bool Video::loadVolatile()
{
GLuint textures[3];
glGenTextures(3, textures);
for (int i = 0; i < 3; i++)
textureHandles[i] = textures[i];
// Create the textures using the initial frame data.
auto frame = (const love::video::VideoStream::Frame*) stream->getFrontBuffer();
int widths[3] = {frame->yw, frame->cw, frame->cw};
int heights[3] = {frame->yh, frame->ch, frame->ch};
const unsigned char *data[3] = {frame->yplane, frame->cbplane, frame->crplane};
Texture::Wrap wrap; // Clamp wrap mode.
bool srgb = false;
OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(PIXELFORMAT_R8, false, srgb);
for (int i = 0; i < 3; i++)
{
gl.bindTextureToUnit(textures[i], 0, false);
gl.setTextureFilter(filter);
gl.setTextureWrap(wrap);
glTexImage2D(GL_TEXTURE_2D, 0, fmt.internalformat, widths[i], heights[i],
0, fmt.externalformat, fmt.type, data[i]);
}
return true;
}
void Video::unloadVolatile()
{
for (int i = 0; i < 3; i++)
{
gl.deleteTexture((GLuint) textureHandles[i]);
textureHandles[i] = 0;
}
}
void Video::uploadFrame(const love::video::VideoStream::Frame *frame)
{
int widths[3] = {frame->yw, frame->cw, frame->cw};
int heights[3] = {frame->yh, frame->ch, frame->ch};
const unsigned char *data[3] = {frame->yplane, frame->cbplane, frame->crplane};
bool srgb = false;
OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(PIXELFORMAT_R8, false, srgb);
for (int i = 0; i < 3; i++)
{
gl.bindTextureToUnit((GLuint) textureHandles[i], 0, false);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, widths[i], heights[i],
fmt.externalformat, fmt.type, data[i]);
}
}
void Video::setFilter(const Texture::Filter &f)
{
if (!Texture::validateFilter(f, false))
throw love::Exception("Invalid texture filter.");
filter = f;
for (int i = 0; i < 3; i++)
{
gl.bindTextureToUnit((GLuint) textureHandles[i], 0, false);
gl.setTextureFilter(filter);
}
}
} // opengl
} // graphics
} // love
-58
View File
@@ -1,58 +0,0 @@
/**
* Copyright (c) 2006-2017 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
**/
#pragma once
// LOVE
#include "graphics/Video.h"
#include "graphics/Volatile.h"
#include "OpenGL.h"
namespace love
{
namespace graphics
{
namespace opengl
{
class Video : public love::graphics::Video, public Volatile
{
public:
Video(love::video::VideoStream *stream, float pixeldensity = 1.0f);
virtual ~Video();
// Volatile
bool loadVolatile() override;
void unloadVolatile() override;
void setFilter(const Texture::Filter &f) override;
private:
void uploadFrame(const love::video::VideoStream::Frame *frame) override;
Texture::Filter filter;
}; // Video
} // opengl
} // graphics
} // love
+42 -28
View File
@@ -31,18 +31,6 @@ Canvas *luax_checkcanvas(lua_State *L, int idx)
return luax_checktype<Canvas>(L, idx);
}
int w_Canvas_getFormat(lua_State *L)
{
Canvas *canvas = luax_checkcanvas(L, 1);
PixelFormat format = canvas->getPixelFormat();
const char *str;
if (!getConstant(format, str))
return luaL_error(L, "Unknown pixel format.");
lua_pushstring(L, str);
return 1;
}
int w_Canvas_getMSAA(lua_State *L)
{
Canvas *canvas = luax_checkcanvas(L, 1);
@@ -52,28 +40,37 @@ int w_Canvas_getMSAA(lua_State *L)
int w_Canvas_renderTo(lua_State *L)
{
Canvas *canvas = luax_checkcanvas(L, 1);
luaL_checktype(L, 2, LUA_TFUNCTION);
Graphics::RenderTarget rt(luax_checkcanvas(L, 1));
int startidx = 2;
if (rt.canvas->getTextureType() != TEXTURE_2D)
{
rt.slice = (int) luaL_checknumber(L, 2) - 1;
startidx++;
}
luaL_checktype(L, startidx, LUA_TFUNCTION);
auto graphics = Module::getInstance<Graphics>(Module::M_GRAPHICS);
if (graphics)
{
// Save the current Canvas so we can restore it when we're done.
std::vector<Canvas *> oldcanvases = graphics->getCanvas();
// Save the current render targets so we can restore them when we're done.
std::vector<Graphics::RenderTarget> oldtargets = graphics->getCanvas();
for (Canvas *c : oldcanvases)
c->retain();
for (auto c : oldtargets)
c.canvas->retain();
luax_catchexcept(L, [&](){ graphics->setCanvas(canvas); });
luax_catchexcept(L, [&](){ graphics->setCanvas(rt); });
lua_settop(L, 2); // make sure the function is on top of the stack
int status = lua_pcall(L, 0, 0, 0);
graphics->setCanvas(oldcanvases);
graphics->setCanvas(oldtargets);
for (Canvas *c : oldcanvases)
c->release();
for (auto c : oldtargets)
c.canvas->release();
if (status != 0)
return lua_error(L);
@@ -86,13 +83,31 @@ int w_Canvas_newImageData(lua_State *L)
{
Canvas *canvas = luax_checkcanvas(L, 1);
love::image::Image *image = luax_getmodule<love::image::Image>(L, love::image::Image::type);
int x = (int) luaL_optnumber(L, 2, 0);
int y = (int) luaL_optnumber(L, 3, 0);
int w = (int) luaL_optnumber(L, 4, canvas->getPixelWidth());
int h = (int) luaL_optnumber(L, 5, canvas->getPixelHeight());
int slice = 0;
int x = 0;
int y = 0;
int w = canvas->getPixelWidth();
int h = canvas->getPixelHeight();
int startidx = 2;
if (canvas->getTextureType() != TEXTURE_2D)
{
slice = (int) luaL_checknumber(L, startidx);
startidx++;
}
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);
}
love::image::ImageData *img = nullptr;
luax_catchexcept(L, [&](){ img = canvas->newImageData(image, x, y, w, h); });
luax_catchexcept(L, [&](){ img = canvas->newImageData(image, slice, x, y, w, h); });
luax_pushtype(L, img);
img->release();
@@ -101,7 +116,6 @@ int w_Canvas_newImageData(lua_State *L)
static const luaL_Reg w_Canvas_functions[] =
{
{ "getFormat", w_Canvas_getFormat },
{ "getMSAA", w_Canvas_getMSAA },
{ "renderTo", w_Canvas_renderTo },
{ "newImageData", w_Canvas_newImageData },
+435 -152
View File
@@ -216,26 +216,67 @@ int w_setCanvas(lua_State *L)
}
bool is_table = lua_istable(L, 1);
std::vector<Canvas *> canvases;
std::vector<Graphics::RenderTarget> targets;
if (is_table)
{
lua_rawgeti(L, 1, 1);
bool table_of_tables = lua_istable(L, -1);
lua_pop(L, 1);
for (int i = 1; i <= (int) luax_objlen(L, 1); i++)
{
lua_rawgeti(L, 1, i);
canvases.push_back(luax_checkcanvas(L, -1));
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);
}
else
{
targets.emplace_back(luax_checkcanvas(L, -1), 0);
if (targets.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);
}
}
else
{
for (int i = 1; i <= lua_gettop(L); i++)
canvases.push_back(luax_checkcanvas(L, i));
{
Graphics::RenderTarget target(luax_checkcanvas(L, i), 0);
TextureType type = target.canvas->getTextureType();
if (i == 1 && type != TEXTURE_2D)
{
target.slice = (int) luaL_checknumber(L, 2) - 1;
targets.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);
}
}
luax_catchexcept(L, [&]() {
if (canvases.size() > 0)
instance()->setCanvas(canvases);
if (targets.size() > 0)
instance()->setCanvas(targets);
else
instance()->setCanvas();
});
@@ -245,22 +286,63 @@ int w_setCanvas(lua_State *L)
int w_getCanvas(lua_State *L)
{
const std::vector<Canvas *> canvases = instance()->getCanvas();
int n = 0;
const std::vector<Graphics::RenderTarget> targets = instance()->getCanvas();
int ntargets = (int) targets.size();
for (Canvas *c : canvases)
{
luax_pushtype(L, c);
n++;
}
if (n == 0)
if (ntargets == 0)
{
lua_pushnil(L);
n = 1;
return 1;
}
bool hasNon2DTextureType = false;
for (const auto &rt : targets)
{
if (rt.canvas->getTextureType() != TEXTURE_2D)
{
hasNon2DTextureType = true;
break;
}
}
if (hasNon2DTextureType)
{
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");
}
lua_rawseti(L, -2, i + 1);
}
return 1;
}
else
{
for (const auto &rt : targets)
luax_pushtype(L, rt.canvas);
return ntargets;
}
return n;
}
static void screenshotCallback(love::image::ImageData *i, Reference *ref, void *gd)
@@ -413,130 +495,325 @@ int w_getStencilTest(lua_State *L)
return 2;
}
static void parsePixelDensity(love::filesystem::FileData *d, float *pixeldensity)
{
// Parse a density scale of 2.0 from "image@2x.png".
const std::string &fname = d->getName();
size_t namelen = fname.length();
size_t atpos = fname.rfind('@');
if (atpos != std::string::npos && atpos + 2 < namelen
&& (fname[namelen - 1] == 'x' || fname[namelen - 1] == 'X'))
{
char *end = nullptr;
long density = strtol(fname.c_str() + atpos + 1, &end, 10);
if (end != nullptr && density > 0 && pixeldensity != nullptr)
*pixeldensity = (float) density;
}
}
static Image::Settings w__optImageSettings(lua_State *L, int idx, const Image::Settings &s)
{
Image::Settings settings = s;
if (!lua_isnoneornil(L, idx))
{
luaL_checktype(L, idx, LUA_TTABLE);
settings.mipmaps = luax_boolflag(L, idx, "mipmaps", s.mipmaps);
settings.linear = luax_boolflag(L, idx, "linear", s.linear);
settings.pixeldensity = (float) luax_numberflag(L, idx, "pixeldensity", s.pixeldensity);
}
return settings;
}
static std::pair<StrongRef<love::image::ImageData>, StrongRef<love::image::CompressedImageData>>
getImageData(lua_State *L, int idx, bool allowcompressed, float *density)
{
StrongRef<love::image::ImageData> idata;
StrongRef<love::image::CompressedImageData> cdata;
// Convert to ImageData / CompressedImageData, if necessary.
if (lua_isstring(L, idx) || luax_istype(L, idx, love::filesystem::File::type) || luax_istype(L, idx, love::filesystem::FileData::type))
{
auto imagemodule = Module::getInstance<love::image::Image>(Module::M_IMAGE);
if (imagemodule == nullptr)
luaL_error(L, "Cannot load images without the love.image module.");
StrongRef<love::filesystem::FileData> fdata(love::filesystem::luax_getfiledata(L, idx), Acquire::NORETAIN);
if (density != nullptr)
parsePixelDensity(fdata, density);
if (allowcompressed && imagemodule->isCompressed(fdata))
luax_catchexcept(L, [&]() { cdata.set(imagemodule->newCompressedData(fdata), Acquire::NORETAIN); });
else
luax_catchexcept(L, [&]() { idata.set(imagemodule->newImageData(fdata), Acquire::NORETAIN); });
}
else if (luax_istype(L, idx, love::image::CompressedImageData::type))
cdata.set(love::image::luax_checkcompressedimagedata(L, idx));
else
idata.set(love::image::luax_checkimagedata(L, idx));
return std::make_pair(idata, cdata);
}
static int w__pushNewImage(lua_State *L, Image::Slices &slices, const Image::Settings &settings)
{
StrongRef<Image> i;
luax_catchexcept(L,
[&]() { i.set(instance()->newImage(slices, settings), Acquire::NORETAIN); },
[&](bool) { slices.clear(); }
);
luax_pushtype(L, i);
return 1;
}
int w_newCubeImage(lua_State *L)
{
luax_checkgraphicscreated(L);
Image::Slices slices(TEXTURE_CUBE);
Image::Settings settings;
auto imagemodule = Module::getInstance<love::image::Image>(Module::M_IMAGE);
if (!lua_istable(L, 1))
{
auto imagedata = getImageData(L, 1, false, &settings.pixeldensity);
std::vector<StrongRef<love::image::ImageData>> faces;
luax_catchexcept(L, [&](){ faces = imagemodule->newCubeFaces(imagedata.first); });
for (int i = 0; i < (int) faces.size(); i++)
slices.set(i, 0, faces[i]);
}
else
{
int tlen = (int) luax_objlen(L, 1);
if (luax_isarrayoftables(L, 1))
{
if (tlen != 6)
return luaL_error(L, "Cubemap images must have 6 faces.");
for (int face = 0; face < tlen; face++)
{
lua_rawgeti(L, 1, face + 1);
luaL_checktype(L, -1, LUA_TTABLE);
int miplen = std::max(1, (int) luax_objlen(L, -1));
for (int mip = 0; mip < miplen; mip++)
{
lua_rawgeti(L, -1, mip + 1);
auto data = getImageData(L, -1, true, face == 0 && mip == 0 ? &settings.pixeldensity : nullptr);
if (data.first.get())
slices.set(face, mip, data.first);
else
slices.set(face, mip, data.second->getSlice(0, 0));
lua_pop(L, 1);
}
}
}
else
{
bool usemipmaps = false;
for (int i = 0; i < tlen; i++)
{
lua_rawgeti(L, 1, i + 1);
auto data = getImageData(L, -1, true, i == 0 ? &settings.pixeldensity : nullptr);
if (data.first.get())
{
if (usemipmaps || data.first->getWidth() != data.first->getHeight())
{
usemipmaps = true;
std::vector<StrongRef<love::image::ImageData>> faces;
luax_catchexcept(L, [&](){ faces = imagemodule->newCubeFaces(data.first); });
for (int face = 0; face < (int) faces.size(); face++)
slices.set(face, i, faces[i]);
}
else
slices.set(i, 0, data.first);
}
else
slices.add(data.second, i, 0, false, true);
}
}
lua_pop(L, tlen);
}
settings = w__optImageSettings(L, 2, settings);
return w__pushNewImage(L, slices, settings);
}
int w_newArrayImage(lua_State *L)
{
luax_checkgraphicscreated(L);
Image::Slices slices(TEXTURE_2D_ARRAY);
Image::Settings settings;
if (lua_istable(L, 1))
{
int tlen = std::max(1, (int) luax_objlen(L, 1));
if (luax_isarrayoftables(L, 1))
{
for (int slice = 0; slice < tlen; slice++)
{
lua_rawgeti(L, 1, slice + 1);
luaL_checktype(L, -1, LUA_TTABLE);
int miplen = std::max(1, (int) luax_objlen(L, -1));
for (int mip = 0; mip < miplen; mip++)
{
lua_rawgeti(L, -1, mip + 1);
auto data = getImageData(L, -1, true, slice == 0 && mip == 0 ? &settings.pixeldensity : nullptr);
if (data.first.get())
slices.set(slice, mip, data.first);
else
slices.set(slice, mip, data.second->getSlice(0, 0));
lua_pop(L, 1);
}
}
}
else
{
for (int slice = 0; slice < tlen; slice++)
{
lua_rawgeti(L, 1, slice + 1);
auto data = getImageData(L, -1, true, slice == 0 ? &settings.pixeldensity : nullptr);
if (data.first.get())
slices.set(slice, 0, data.first);
else
slices.add(data.second, slice, 0, false, true);
}
}
lua_pop(L, tlen);
}
else
{
auto data = getImageData(L, 1, true, &settings.pixeldensity);
if (data.first.get())
slices.set(0, 0, data.first);
else
slices.add(data.second, 0, 0, true, true);
}
settings = w__optImageSettings(L, 2, settings);
return w__pushNewImage(L, slices, settings);
}
int w_newVolumeImage(lua_State *L)
{
luax_checkgraphicscreated(L);
Image::Slices slices(TEXTURE_VOLUME);
Image::Settings settings;
if (lua_istable(L, 1))
{
int tlen = std::max(1, (int) luax_objlen(L, 1));
if (luax_isarrayoftables(L, 1))
{
for (int mip = 0; mip < tlen; mip++)
{
lua_rawgeti(L, 1, mip + 1);
luaL_checktype(L, -1, LUA_TTABLE);
int slicelen = std::max(1, (int) luax_objlen(L, -1));
for (int slice = 0; slice < slicelen; slice++)
{
lua_rawgeti(L, -1, mip + 1);
auto data = getImageData(L, -1, true, slice == 0 && mip == 0 ? &settings.pixeldensity : nullptr);
if (data.first.get())
slices.set(slice, mip, data.first);
else
slices.set(slice, mip, data.second->getSlice(0, 0));
lua_pop(L, 1);
}
}
}
else
{
for (int layer = 0; layer < tlen; layer++)
{
lua_rawgeti(L, 1, layer + 1);
auto data = getImageData(L, -1, true, layer == 0 ? &settings.pixeldensity : nullptr);
if (data.first.get())
slices.set(layer, 0, data.first);
else
slices.add(data.second, layer, 0, false, true);
}
}
lua_pop(L, tlen);
}
else
{
auto data = getImageData(L, 1, true, &settings.pixeldensity);
if (data.first.get())
slices.set(0, 0, data.first);
else
slices.add(data.second, 0, 0, true, true);
}
settings = w__optImageSettings(L, 2, settings);
return w__pushNewImage(L, slices, settings);
}
int w_newImage(lua_State *L)
{
luax_checkgraphicscreated(L);
std::vector<love::image::ImageData *> data;
std::vector<love::image::CompressedImageData *> cdata;
Image::Slices slices(TEXTURE_2D);
Image::Settings settings;
bool releasedata = false;
// Convert to ImageData / CompressedImageData, if necessary.
if (lua_isstring(L, 1) || luax_istype(L, 1, love::filesystem::File::type) || luax_istype(L, 1, love::filesystem::FileData::type))
if (lua_istable(L, 1))
{
auto imagemodule = Module::getInstance<love::image::Image>(Module::M_IMAGE);
if (imagemodule == nullptr)
return luaL_error(L, "Cannot load images without the love.image module.");
love::filesystem::FileData *fdata = love::filesystem::luax_getfiledata(L, 1);
// Parse a density scale of 2.0 from "image@2x.png".
const std::string &fname = fdata->getName();
size_t namelen = fname.length();
size_t atpos = fname.rfind('@');
if (atpos != std::string::npos && atpos + 2 < namelen
&& (fname[namelen - 1] == 'x' || fname[namelen - 1] == 'X'))
int n = std::max(1, (int) luax_objlen(L, 1));
for (int i = 0; i < n; i++)
{
char *end = nullptr;
long density = strtol(fname.c_str() + atpos + 1, &end, 10);
if (end != nullptr && density > 0)
settings.pixeldensity = (float) density;
lua_rawgeti(L, 1, i + 1);
auto data = getImageData(L, -1, true, i == 0 ? &settings.pixeldensity : nullptr);
if (data.first.get())
slices.set(0, i, data.first);
else
slices.set(0, i, data.second->getSlice(0, 0));
}
if (imagemodule->isCompressed(fdata))
{
luax_catchexcept(L,
[&]() { cdata.push_back(imagemodule->newCompressedData(fdata)); },
[&](bool) { fdata->release(); }
);
}
else
{
luax_catchexcept(L,
[&]() { data.push_back(imagemodule->newImageData(fdata)); },
[&](bool) { fdata->release(); }
);
}
// Lua's GC won't release the image data, so we should do it ourselves.
releasedata = true;
lua_pop(L, n);
}
else if (luax_istype(L, 1, love::image::CompressedImageData::type))
cdata.push_back(love::image::luax_checkcompressedimagedata(L, 1));
else
data.push_back(love::image::luax_checkimagedata(L, 1));
if (!lua_isnoneornil(L, 2))
{
luaL_checktype(L, 2, LUA_TTABLE);
settings.mipmaps = luax_boolflag(L, 2, luax_imageSettingName(Image::SETTING_MIPMAPS), settings.mipmaps);
settings.linear = luax_boolflag(L, 2, luax_imageSettingName(Image::SETTING_LINEAR), settings.linear);
settings.pixeldensity = (float) luax_numberflag(L, 2, luax_imageSettingName(Image::SETTING_PIXELDENSITY), settings.pixeldensity);
lua_getfield(L, 2, luax_imageSettingName(Image::SETTING_MIPMAPS));
// Add all manually specified mipmap images to the array of imagedata.
// i.e. settings = {mipmaps = {mip1, mip2, ...}}.
if (lua_istable(L, -1))
{
for (size_t i = 1; i <= luax_objlen(L, -1); i++)
{
lua_rawgeti(L, -1, i);
if (!data.empty())
{
if (!luax_istype(L, -1, love::image::ImageData::type))
luax_convobj(L, -1, "image", "newImageData");
data.push_back(love::image::luax_checkimagedata(L, -1));
}
else if (!cdata.empty())
{
if (!luax_istype(L, -1, love::image::CompressedImageData::type))
luax_convobj(L, -1, "image", "newCompressedData");
cdata.push_back(love::image::luax_checkcompressedimagedata(L, -1));
}
lua_pop(L, 1);
}
}
lua_pop(L, 1);
auto data = getImageData(L, 1, true, &settings.pixeldensity);
if (data.first.get())
slices.set(0, 0, data.first);
else
slices.add(data.second, 0, 0, false, true);
}
// Create the image.
Image *image = nullptr;
luax_catchexcept(L,
[&]() {
if (!cdata.empty())
image = instance()->newImage(cdata, settings);
else if (!data.empty())
image = instance()->newImage(data, settings);
},
[&](bool) {
if (releasedata)
{
for (auto d : data)
d->release();
for (auto d : cdata)
d->release();
}
}
);
if (image == nullptr)
return luaL_error(L, "Could not load image.");
// Push the type.
luax_pushtype(L, image);
image->release();
return 1;
settings = w__optImageSettings(L, 2, settings);
return w__pushNewImage(L, slices, settings);
}
int w_newQuad(lua_State *L)
@@ -605,18 +882,6 @@ int w_newImageFont(lua_State *L)
// filter for glyphs
Texture::Filter filter = instance()->getDefaultFilter();
// Convert to ImageData if necessary.
if (luax_istype(L, 1, Image::type))
{
Image *i = luax_checktype<Image>(L, 1);
filter = i->getFilter();
const auto &idlevels = i->getImageData();
if (idlevels.empty())
return luaL_argerror(L, 1, "Image must not be compressed.");
luax_pushtype(L, idlevels[0].get());
lua_replace(L, 1);
}
// Convert to Rasterizer if necessary.
if (!luax_istype(L, 1, love::font::Rasterizer::type))
{
@@ -687,35 +952,50 @@ int w_newCanvas(lua_State *L)
{
luax_checkgraphicscreated(L);
// check if width and height are given. else default to screen dimensions.
int width = (int) luaL_optnumber(L, 1, instance()->getWidth());
int height = (int) luaL_optnumber(L, 2, instance()->getHeight());
Canvas::Settings settings;
// check if width and height are given. else default to screen dimensions.
settings.width = (int) luaL_optnumber(L, 1, instance()->getWidth());
settings.height = (int) luaL_optnumber(L, 2, instance()->getHeight());
// Default to the screen's current pixel density scale.
settings.pixeldensity = instance()->getScreenPixelDensity();
if (!lua_isnoneornil(L, 3))
int startidx = 3;
if (lua_isnumber(L, 3))
{
lua_getfield(L, 3, "format");
settings.layers = (int) luaL_checknumber(L, 3);
settings.type = TEXTURE_2D_ARRAY;
startidx = 4;
}
if (!lua_isnoneornil(L, startidx))
{
settings.pixeldensity = (float) luax_numberflag(L, startidx, "pixeldensity", settings.pixeldensity);
settings.msaa = luax_intflag(L, startidx, "msaa", 0);
lua_getfield(L, startidx, "format");
if (!lua_isnoneornil(L, -1))
{
const char *str = luaL_checkstring(L, -1);
if (!getConstant(str, settings.format))
return luaL_error(L, "Invalid Canvas format: %s", str);
return luaL_error(L, "Invalid pixel format: %s", str);
}
lua_pop(L, 1);
settings.pixeldensity = (float) luax_numberflag(L, 3, "pixeldensity", settings.pixeldensity);
settings.msaa = luax_intflag(L, 3, "msaa", settings.msaa);
lua_getfield(L, startidx, "type");
if (!lua_isnoneornil(L, -1))
{
const char *str = luaL_checkstring(L, -1);
if (!Texture::getConstant(str, settings.type))
return luaL_error(L, "Invalid texture type: %s", str);
}
lua_pop(L, 1);
}
Canvas *canvas = nullptr;
luax_catchexcept(L, [&](){ canvas = instance()->newCanvas(width, height, settings); });
if (canvas == nullptr)
return luaL_error(L, "Canvas not created, but no error thrown. I don't even...");
luax_catchexcept(L, [&](){ canvas = instance()->newCanvas(settings); });
luax_pushtype(L, canvas);
canvas->release();
@@ -2178,6 +2458,9 @@ static const luaL_Reg functions[] =
{ "present", w_present },
{ "newImage", w_newImage },
{ "newArrayImage", w_newArrayImage },
{ "newVolumeImage", w_newVolumeImage },
{ "newCubeImage", w_newCubeImage },
{ "newQuad", w_newQuad },
{ "newFont", w_newFont },
{ "newImageFont", w_newImageFont },
+38 -12
View File
@@ -42,7 +42,17 @@ GLSL.SYNTAX = [[
#endif
#define number float
#define Image sampler2D
#define extern uniform]]
#define ArrayImage sampler2DArray
#define CubeImage samplerCube
#define VolumeImage sampler3D
#define extern uniform
#ifdef GL_EXT_texture_array
#extension GL_EXT_texture_array : enable
#endif
#ifdef GL_OES_texture_3D
#extension GL_OES_texture_3D : enable
#endif
]]
-- Uniforms shared by the vertex and pixel shader stages.
GLSL.UNIFORMS = [[
@@ -66,13 +76,36 @@ GLSL.FUNCTIONS = [[
#else
#if __VERSION__ >= 130
#define texture2D Texel
#define texture3D Texel
#define textureCube Texel
#define texture2DArray Texel
#define love_texture2D texture
#define love_texture3D texture
#define love_textureCube texture
#define love_texture2DArray texture
#else
#define love_texture2D texture2D
#define love_texture3D texture3D
#define love_textureCube textureCube
#define love_texture2DArray texture2DArray
#endif
vec4 Texel(sampler2D s, vec2 c) { return love_texture2D(s, c); }
vec4 Texel(samplerCube s, vec3 c) { return love_textureCube(s, c); }
#if __VERSION__ > 100 || defined(GL_OES_texture_3D)
vec4 Texel(sampler3D s, vec3 c) { return love_texture3D(s, c); }
#endif
#if __VERSION__ >= 130 || defined(GL_EXT_texture_array)
vec4 Texel(sampler2DArray s, vec3 c) { return love_texture2DArray(s, c); }
#endif
#ifdef PIXEL
vec4 Texel(sampler2D s, vec2 c, float b) { return love_texture2D(s, c, b); }
vec4 Texel(samplerCube s, vec3 c, float b) { return love_textureCube(s, c, b); }
#if __VERSION__ > 100 || defined(GL_OES_texture_3D)
vec4 Texel(sampler3D s, vec3 c, float b) { return love_texture3D(s, c, b); }
#endif
#if __VERSION__ >= 130 || defined(GL_EXT_texture_array)
vec4 Texel(sampler2DArray s, vec3 c, float b) { return love_texture2DArray(s, c, b); }
#endif
#endif
#define texture love_texture
#endif
@@ -187,17 +220,11 @@ GLSL.PIXEL = {
#if __VERSION__ >= 130
#define varying in
#ifdef LOVE_MULTI_CANVAS
layout(location = 0) out vec4 love_Canvases[love_MaxCanvases];
#else
layout(location = 0) out vec4 love_PixelColor;
#endif
layout(location = 0) out vec4 love_Canvases[love_MaxCanvases];
#define love_PixelColor love_Canvases[0]
#else
#ifdef LOVE_MULTI_CANVAS
#define love_Canvases gl_FragData
#else
#define love_PixelColor gl_FragColor
#endif
#define love_Canvases gl_FragData
#define love_PixelColor gl_FragColor
#endif
// See Shader::updateScreenParams in Shader.cpp.
@@ -259,7 +286,6 @@ local function createShaderStageCode(stage, code, lang, gles, glsl1on3, gammacor
"#define "..stage,
glsl1on3 and "#define LOVE_GLSL1_ON_GLSL3 1" or "",
gammacorrect and "#define LOVE_GAMMA_CORRECT 1" or "",
multicanvas and "#define LOVE_MULTI_CANVAS 1" or "",
GLSL.SYNTAX,
GLSL[stage].HEADER,
GLSL.UNIFORMS,
+21 -95
View File
@@ -31,40 +31,11 @@ Image *luax_checkimage(lua_State *L, int idx)
return luax_checktype<Image>(L, idx);
}
int w_Image_setMipmapFilter(lua_State *L)
int w_Image_isFormatLinear(lua_State *L)
{
Image *t = luax_checkimage(L, 1);
Texture::Filter f = t->getFilter();
if (lua_isnoneornil(L, 2))
f.mipmap = Texture::FILTER_NONE; // mipmapping is disabled if no argument is given
else
{
const char *mipmapstr = luaL_checkstring(L, 2);
if (!Texture::getConstant(mipmapstr, f.mipmap))
return luaL_error(L, "Invalid filter mode: %s", mipmapstr);
}
luax_catchexcept(L, [&](){ t->setFilter(f); });
t->setMipmapSharpness((float) luaL_optnumber(L, 3, 0.0));
return 0;
}
int w_Image_getMipmapFilter(lua_State *L)
{
Image *t = luax_checkimage(L, 1);
const Texture::Filter &f = t->getFilter();
const char *mipmapstr;
if (Texture::getConstant(f.mipmap, mipmapstr))
lua_pushstring(L, mipmapstr);
else
lua_pushnil(L); // only return a mipmap filter if mipmapping is enabled
lua_pushnumber(L, t->getMipmapSharpness());
return 2;
Image *i = luax_checkimage(L, 1);
luax_pushboolean(L, i->isFormatLinear());
return 1;
}
int w_Image_isCompressed(lua_State *L)
@@ -74,78 +45,33 @@ int w_Image_isCompressed(lua_State *L)
return 1;
}
int w_Image_refresh(lua_State *L)
int w_Image_replacePixels(lua_State *L)
{
Image *i = luax_checkimage(L, 1);
love::image::ImageData *id = luax_checktype<love::image::ImageData>(L, 2);
int xoffset = (int) luaL_optnumber(L, 2, 0);
int yoffset = (int) luaL_optnumber(L, 3, 0);
int w = (int) luaL_optnumber(L, 4, i->getWidth());
int h = (int) luaL_optnumber(L, 5, i->getHeight());
int slice = 0;
int mipmap = 0;
bool reloadmipmaps = i->getMipmapsType() == Image::MIPMAPS_GENERATED;
luax_catchexcept(L, [&](){ i->refresh(xoffset, yoffset, w, h); });
if (i->getTextureType() != TEXTURE_2D)
{
slice = (int) luaL_checknumber(L, 3) - 1;
if (!reloadmipmaps)
mipmap = (int) luaL_optnumber(L, 4, 1) - 1;
}
else if (!reloadmipmaps)
mipmap = (int) luaL_optnumber(L, 3, 1) - 1;
luax_catchexcept(L, [&](){ i->replacePixels(id, slice, mipmap, reloadmipmaps); });
return 0;
}
int w_Image_getData(lua_State *L)
{
Image *i = luax_checkimage(L, 1);
int n = 0;
if (i->isCompressed())
{
for (const auto &cdata : i->getCompressedData())
{
luax_pushtype(L, cdata.get());
n++;
}
}
else
{
for (const auto &data : i->getImageData())
{
luax_pushtype(L, data.get());
n++;
}
}
return n;
}
const char *luax_imageSettingName(Image::SettingType settingtype)
{
const char *name = nullptr;
Image::getConstant(settingtype, name);
return name;
}
int w_Image_getFlags(lua_State *L)
{
Image *i = luax_checkimage(L, 1);
Image::Settings settings = i->getFlags();
lua_createtable(L, 0, 2);
lua_pushboolean(L, settings.mipmaps);
lua_setfield(L, -2, luax_imageSettingName(Image::SETTING_MIPMAPS));
lua_pushboolean(L, settings.linear);
lua_setfield(L, -2, luax_imageSettingName(Image::SETTING_LINEAR));
lua_pushnumber(L, settings.pixeldensity);
lua_setfield(L, -2, luax_imageSettingName(Image::SETTING_PIXELDENSITY));
return 1;
}
static const luaL_Reg w_Image_functions[] =
{
{ "setMipmapFilter", w_Image_setMipmapFilter },
{ "getMipmapFilter", w_Image_getMipmapFilter },
{ "isFormatLinear", w_Image_isFormatLinear },
{ "isCompressed", w_Image_isCompressed },
{ "refresh", w_Image_refresh },
{ "getData", w_Image_getData },
{ "getFlags", w_Image_getFlags },
{ "replacePixels", w_Image_replacePixels },
{ 0, 0 }
};
-1
View File
@@ -30,7 +30,6 @@ namespace love
namespace graphics
{
const char *luax_imageSettingName(Image::SettingType settingtype);
Image *luax_checkimage(lua_State *L, int idx);
extern "C" int luaopen_image(lua_State *L);
+1 -1
View File
@@ -456,7 +456,7 @@ int w_Mesh_setTexture(lua_State *L)
else
{
Texture *tex = luax_checktexture(L, 2);
t->setTexture(tex);
luax_catchexcept(L, [&](){ t->setTexture(tex); });
}
return 0;
+1 -1
View File
@@ -55,7 +55,7 @@ int w_ParticleSystem_setTexture(lua_State *L)
{
ParticleSystem *t = luax_checkparticlesystem(L, 1);
Texture *tex = luax_checktexture(L, 2);
t->setTexture(tex);
luax_catchexcept(L, [&](){ t->setTexture(tex); });
return 0;
}
+6 -1
View File
@@ -267,7 +267,12 @@ int w_Shader_sendTextures(lua_State *L, int startidx, Shader *shader, const Shad
textures.reserve(count);
for (int i = 0; i < count; i++)
textures.push_back(luax_checktexture(L, startidx + i));
{
Texture *tex = luax_checktexture(L, startidx + i);
if (tex->getTextureType() != info->textureType)
return luaL_argerror(L, startidx + i, "invalid texture type for uniform");
textures.push_back(tex);
}
luax_catchexcept(L, [&]() { shader->sendTextures(info, textures.data(), count); });
return 0;
+1 -1
View File
@@ -120,7 +120,7 @@ int w_SpriteBatch_setTexture(lua_State *L)
{
SpriteBatch *t = luax_checkspritebatch(L, 1);
Texture *tex = luax_checktexture(L, 2);
t->setTexture(tex);
luax_catchexcept(L, [&](){ t->setTexture(tex); });
return 0;
}
+90
View File
@@ -30,6 +30,16 @@ Texture *luax_checktexture(lua_State *L, int idx)
return luax_checktype<Texture>(L, idx);
}
int w_Texture_getTextureType(lua_State *L)
{
Texture *t = luax_checktexture(L, 1);
const char *tstr;
if (!Texture::getConstant(t->getTextureType(), tstr))
return luaL_error(L, "unknown texture type");
lua_pushstring(L, tstr);
return 1;
}
int w_Texture_getWidth(lua_State *L)
{
Texture *t = luax_checktexture(L, 1);
@@ -52,6 +62,27 @@ int w_Texture_getDimensions(lua_State *L)
return 2;
}
int w_Texture_getDepth(lua_State *L)
{
Texture *t = luax_checktexture(L, 1);
lua_pushnumber(L, t->getDepth());
return 1;
}
int w_Texture_getLayerCount(lua_State *L)
{
Texture *t = luax_checktexture(L, 1);
lua_pushnumber(L, t->getLayerCount());
return 1;
}
int w_Texture_getMipmapCount(lua_State *L)
{
Texture *t = luax_checktexture(L, 1);
lua_pushnumber(L, t->getMipmapCount());
return 1;
}
int w_Texture_getPixelWidth(lua_State *L)
{
Texture *t = luax_checktexture(L, 1);
@@ -119,6 +150,42 @@ int w_Texture_getFilter(lua_State *L)
return 3;
}
int w_Texture_setMipmapFilter(lua_State *L)
{
Texture *t = luax_checktexture(L, 1);
Texture::Filter f = t->getFilter();
if (lua_isnoneornil(L, 2))
f.mipmap = Texture::FILTER_NONE; // mipmapping is disabled if no argument is given
else
{
const char *mipmapstr = luaL_checkstring(L, 2);
if (!Texture::getConstant(mipmapstr, f.mipmap))
return luaL_error(L, "Invalid filter mode: %s", mipmapstr);
}
luax_catchexcept(L, [&](){ t->setFilter(f); });
t->setMipmapSharpness((float) luaL_optnumber(L, 3, 0.0));
return 0;
}
int w_Texture_getMipmapFilter(lua_State *L)
{
Texture *t = luax_checktexture(L, 1);
const Texture::Filter &f = t->getFilter();
const char *mipmapstr;
if (Texture::getConstant(f.mipmap, mipmapstr))
lua_pushstring(L, mipmapstr);
else
lua_pushnil(L); // only return a mipmap filter if mipmapping is enabled
lua_pushnumber(L, t->getMipmapSharpness());
return 2;
}
int w_Texture_setWrap(lua_State *L)
{
Texture *t = luax_checktexture(L, 1);
@@ -143,30 +210,53 @@ int w_Texture_getWrap(lua_State *L)
const char *sstr = nullptr;
const char *tstr = nullptr;
const char *rstr = nullptr;
if (!Texture::getConstant(w.s, sstr))
return luaL_error(L, "Unknown wrap mode.");
if (!Texture::getConstant(w.t, tstr))
return luaL_error(L, "Unknown wrap mode.");
if (!Texture::getConstant(w.r, rstr))
return luaL_error(L, "Unknown wrap mode.");
lua_pushstring(L, sstr);
lua_pushstring(L, tstr);
lua_pushstring(L, rstr);
return 2;
}
int w_Texture_getFormat(lua_State *L)
{
Texture *t = luax_checktexture(L, 1);
PixelFormat format = t->getPixelFormat();
const char *str;
if (!getConstant(format, str))
return luaL_error(L, "Unknown pixel format.");
lua_pushstring(L, str);
return 1;
}
const luaL_Reg w_Texture_functions[] =
{
{ "getTextureType", w_Texture_getTextureType },
{ "getWidth", w_Texture_getWidth },
{ "getHeight", w_Texture_getHeight },
{ "getDimensions", w_Texture_getDimensions },
{ "getDepth", w_Texture_getDepth },
{ "getLayerCount", w_Texture_getLayerCount },
{ "getMipmapCount", w_Texture_getMipmapCount },
{ "getPixelWidth", w_Texture_getPixelWidth },
{ "getPixelHeight", w_Texture_getPixelHeight },
{ "getPixelDimensions", w_Texture_getPixelDimensions },
{ "getPixelDensity", w_Texture_getPixelDensity },
{ "setFilter", w_Texture_setFilter },
{ "getFilter", w_Texture_getFilter },
{ "setMipmapFilter", w_Texture_setMipmapFilter },
{ "getMipmapFilter", w_Texture_getMipmapFilter },
{ "setWrap", w_Texture_setWrap },
{ "getWrap", w_Texture_getWrap },
{ "getFormat", w_Texture_getFormat },
{ 0, 0 }
};