mirror of
https://github.com/love2d/love.git
synced 2026-08-19 04:06:17 +02:00
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:
@@ -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);
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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:
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user