Add love.graphics.copyTextureToBuffer and copyBufferToTexture.

This commit is contained in:
Alex Szpakowski
2021-12-28 12:46:56 -04:00
parent d7bfc2ebcb
commit 52101b2563
11 changed files with 344 additions and 4 deletions
+4 -2
View File
@@ -42,7 +42,8 @@ ByteData::ByteData(const void *d, size_t size)
: size(size)
{
create();
memcpy(data, d, size);
if (d != nullptr)
memcpy(data, d, size);
}
ByteData::ByteData(void *d, size_t size, bool own)
@@ -53,7 +54,8 @@ ByteData::ByteData(void *d, size_t size, bool own)
else
{
create();
memcpy(data, d, size);
if (d != nullptr)
memcpy(data, d, size);
}
}
+166
View File
@@ -1076,6 +1076,169 @@ void Graphics::copyBuffer(Buffer *source, Buffer *dest, size_t sourceoffset, siz
source->copyTo(dest, sourceoffset, destoffset, size);
}
void Graphics::copyTextureToBuffer(Texture *source, Buffer *dest, int slice, int mipmap, const Rect &rect, size_t destoffset, int destwidth)
{
if (!capabilities.features[FEATURE_COPY_TEXTURE_TO_BUFFER])
{
if (!source->isRenderTarget())
throw love::Exception("Copying a non-render target Texture to a Buffer is not supported on this system.");
if (!capabilities.features[FEATURE_COPY_RENDER_TARGET_TO_BUFFER])
throw love::Exception("Copying a render target Texture to a Buffer is not supported on this system.");
}
PixelFormat format = source->getPixelFormat();
if (isPixelFormatDepthStencil(format))
throw love::Exception("Copying a depth/stencil Texture to a Buffer is not supported.");
if (!source->isReadable())
throw love::Exception("copyTextureToBuffer can only be called on readable Textures.");
if (dest->getDataUsage() == BUFFERDATAUSAGE_STREAM)
throw love::Exception("Buffers created with 'stream' data usage cannot be used as a copy destination.");
if (isRenderTargetActive(source))
throw love::Exception("copyTextureToBuffer cannot be called while the Texture is an active render target.");
if (mipmap < 0 || mipmap >= source->getMipmapCount())
throw love::Exception("Invalid texture mipmap index %d.", mipmap + 1);
TextureType textype = source->getTextureType();
if (slice < 0 || (textype == TEXTURE_CUBE && slice >= 6)
|| (textype == TEXTURE_VOLUME && slice >= source->getDepth(mipmap))
|| (textype == TEXTURE_2D_ARRAY && slice >= source->getLayerCount()))
{
throw love::Exception("Invalid texture slice index %d.", slice + 1);
}
int mipw = source->getPixelWidth(mipmap);
int miph = source->getPixelHeight(mipmap);
if (rect.x < 0 || rect.y < 0 || rect.w <= 0 || rect.h <= 0
|| (rect.x + rect.w) > mipw || (rect.y + rect.h) > miph)
{
throw love::Exception("Invalid rectangle dimensions (x=%d, y=%d, w=%d, h=%d) for %dx%d texture.", rect.x, rect.y, rect.w, rect.h, mipw, miph);
}
if (destwidth <= 0)
destwidth = rect.w;
size_t size = 0;
if (isPixelFormatCompressed(format))
{
if (destwidth != rect.w) // OpenGL limitation...
throw love::Exception("Copying a compressed texture to a buffer cannot use a custom destination width.");
const PixelFormatInfo &info = getPixelFormatInfo(format);
int bw = (int) info.blockWidth;
int bh = (int) info.blockHeight;
if (rect.x % bw != 0 || rect.y % bh != 0 ||
((rect.w % bw != 0 || rect.h % bh != 0) && rect.x + rect.w != source->getPixelWidth(mipmap)))
{
const char *name = nullptr;
love::getConstant(format, name);
throw love::Exception("Compressed texture format %s only supports copying a sub-rectangle with offset and dimensions that are a multiple of %d x %d.", name, bw, bh);
}
// Note: this will need to change if destwidth == rect.w restriction
// is removed.
size = getPixelFormatSliceSize(format, destwidth, rect.h);
}
else
{
// Not the cleanest, but should work since uncompressed formats always
// have 1x1 blocks.
int pixels = (rect.h - 1) * destwidth + rect.w;
size = getPixelFormatUncompressedRowSize(format, pixels);
}
Range destrange(destoffset, size);
if (destrange.getMax() >= dest->getSize())
throw love::Exception("Buffer copy destination offset and width/height doesn't fit within the destination Buffer.");
source->copyToBuffer(dest, slice, mipmap, rect, destoffset, destwidth, size);
}
void Graphics::copyBufferToTexture(Buffer *source, Texture *dest, size_t sourceoffset, int sourcewidth, int slice, int mipmap, const Rect &rect)
{
if (!capabilities.features[FEATURE_COPY_BUFFER_TO_TEXTURE])
throw love::Exception("Copying a Buffer to a Texture is not supported on this system.");
PixelFormat format = dest->getPixelFormat();
if (isPixelFormatDepthStencil(format))
throw love::Exception("Copying a Buffer to a depth/stencil Texture is not supported.");
if (!dest->isReadable())
throw love::Exception("copyBufferToTexture can only be called on readable Textures.");
if (isRenderTargetActive(dest))
throw love::Exception("copyBufferToTexture cannot be called while the Texture is an active render target.");
if (mipmap < 0 || mipmap >= dest->getMipmapCount())
throw love::Exception("Invalid texture mipmap index %d.", mipmap + 1);
TextureType textype = dest->getTextureType();
if (slice < 0 || (textype == TEXTURE_CUBE && slice >= 6)
|| (textype == TEXTURE_VOLUME && slice >= dest->getDepth(mipmap))
|| (textype == TEXTURE_2D_ARRAY && slice >= dest->getLayerCount()))
{
throw love::Exception("Invalid texture slice index %d.", slice + 1);
}
int mipw = dest->getPixelWidth(mipmap);
int miph = dest->getPixelHeight(mipmap);
if (rect.x < 0 || rect.y < 0 || rect.w <= 0 || rect.h <= 0
|| (rect.x + rect.w) > mipw || (rect.y + rect.h) > miph)
{
throw love::Exception("Invalid rectangle dimensions (x=%d, y=%d, w=%d, h=%d) for %dx%d texture.", rect.x, rect.y, rect.w, rect.h, mipw, miph);
}
if (sourcewidth <= 0)
sourcewidth = rect.w;
size_t size = 0;
if (isPixelFormatCompressed(format))
{
if (sourcewidth != rect.w) // OpenGL limitation...
throw love::Exception("Copying a buffer to a compressed texture cannot use a custom source width.");
const PixelFormatInfo &info = getPixelFormatInfo(format);
int bw = (int) info.blockWidth;
int bh = (int) info.blockHeight;
if (rect.x % bw != 0 || rect.y % bh != 0 ||
((rect.w % bw != 0 || rect.h % bh != 0) && rect.x + rect.w != dest->getPixelWidth(mipmap)))
{
const char *name = nullptr;
love::getConstant(format, name);
throw love::Exception("Compressed texture format %s only supports copying a sub-rectangle with offset and dimensions that are a multiple of %d x %d.", name, bw, bh);
}
// Note: this will need to change if sourcewidth == rect.w restriction
// is removed.
size = getPixelFormatSliceSize(format, sourcewidth, rect.h);
}
else
{
// Not the cleanest, but should work since uncompressed formats always
// have 1x1 blocks.
int pixels = (rect.h - 1) * sourcewidth + rect.w;
size = getPixelFormatUncompressedRowSize(format, pixels);
}
Range sourcerange(sourceoffset, size);
if (sourcerange.getMax() >= source->getSize())
throw love::Exception("Buffer copy source offset and width/height doesn't fit within the source Buffer.");
dest->copyFromBuffer(source, sourceoffset, sourcewidth, size, slice, mipmap, rect);
}
void Graphics::dispatchThreadgroups(Shader* shader, int x, int y, int z)
{
if (!shader->hasStage(SHADERSTAGE_COMPUTE))
@@ -1907,6 +2070,9 @@ STRINGMAP_CLASS_BEGIN(Graphics, Graphics::Feature, Graphics::FEATURE_MAX_ENUM, f
{ "instancing", Graphics::FEATURE_INSTANCING },
{ "texelbuffer", Graphics::FEATURE_TEXEL_BUFFER },
{ "copybuffer", Graphics::FEATURE_COPY_BUFFER },
{ "copybuffertotexture", Graphics::FEATURE_COPY_BUFFER_TO_TEXTURE },
{ "copytexturetobuffer", Graphics::FEATURE_COPY_TEXTURE_TO_BUFFER },
{ "copyrendertargettobuffer", Graphics::FEATURE_COPY_RENDER_TARGET_TO_BUFFER },
}
STRINGMAP_CLASS_END(Graphics, Graphics::Feature, Graphics::FEATURE_MAX_ENUM, feature)
+5
View File
@@ -145,6 +145,9 @@ public:
FEATURE_INSTANCING,
FEATURE_TEXEL_BUFFER,
FEATURE_COPY_BUFFER,
FEATURE_COPY_BUFFER_TO_TEXTURE,
FEATURE_COPY_TEXTURE_TO_BUFFER,
FEATURE_COPY_RENDER_TARGET_TO_BUFFER,
FEATURE_MAX_ENUM
};
@@ -674,6 +677,8 @@ public:
void captureScreenshot(const ScreenshotInfo &info);
void copyBuffer(Buffer *source, Buffer *dest, size_t sourceoffset, size_t destoffset, size_t size);
void copyTextureToBuffer(Texture *source, Buffer *dest, int slice, int mipmap, const Rect &rect, size_t destoffset, int destwidth);
void copyBufferToTexture(Buffer *source, Texture *dest, size_t sourceoffset, int sourcewidth, int slice, int mipmap, const Rect &rect);
void dispatchThreadgroups(Shader* shader, int x, int y, int z);
+4
View File
@@ -46,6 +46,7 @@ namespace graphics
{
class Graphics;
class Buffer;
enum TextureType
{
@@ -246,6 +247,9 @@ public:
love::image::ImageData *newImageData(love::image::Image *module, int slice, int mipmap, const Rect &rect);
virtual void copyFromBuffer(Buffer *source, size_t sourceoffset, int sourcewidth, size_t size, int slice, int mipmap, const Rect &rect) = 0;
virtual void copyToBuffer(Buffer *dest, int slice, int mipmap, const Rect &rect, size_t destoffset, int destwidth, size_t size) = 0;
virtual ptrdiff_t getRenderTargetHandle() const = 0;
TextureType getTextureType() const;
+4 -1
View File
@@ -1640,7 +1640,10 @@ void Graphics::initCapabilities()
capabilities.features[FEATURE_INSTANCING] = gl.isInstancingSupported();
capabilities.features[FEATURE_TEXEL_BUFFER] = gl.isBufferUsageSupported(BUFFERUSAGE_TEXEL);
capabilities.features[FEATURE_COPY_BUFFER] = gl.isCopyBufferSupported();
static_assert(FEATURE_MAX_ENUM == 12, "Graphics::initCapabilities must be updated when adding a new graphics feature!");
capabilities.features[FEATURE_COPY_BUFFER_TO_TEXTURE] = gl.isCopyBufferToTextureSupported();
capabilities.features[FEATURE_COPY_TEXTURE_TO_BUFFER] = gl.isCopyTextureToBufferSupported();
capabilities.features[FEATURE_COPY_RENDER_TARGET_TO_BUFFER] = gl.isCopyRenderTargetToBufferSupported();
static_assert(FEATURE_MAX_ENUM == 15, "Graphics::initCapabilities must be updated when adding a new graphics feature!");
capabilities.limits[LIMIT_POINT_SIZE] = gl.getMaxPointSize();
capabilities.limits[LIMIT_TEXTURE_SIZE] = gl.getMax2DTextureSize();
+18
View File
@@ -1530,6 +1530,24 @@ bool OpenGL::isCopyBufferSupported() const
return GLAD_VERSION_3_1 || GLAD_ES_VERSION_3_0;
}
bool OpenGL::isCopyBufferToTextureSupported() const
{
// Requires pixel unpack buffer binding support.
return GLAD_VERSION_2_0 || GLAD_ES_VERSION_3_0;
}
bool OpenGL::isCopyTextureToBufferSupported() const
{
// Requires glGetTextureSubImage support.
return GLAD_VERSION_4_5;
}
bool OpenGL::isCopyRenderTargetToBufferSupported() const
{
// Requires pixel pack buffer binding support.
return GLAD_VERSION_2_0 || GLAD_ES_VERSION_3_0;
}
int OpenGL::getMax2DTextureSize() const
{
return std::max(max2DTextureSize, 1);
+3
View File
@@ -372,6 +372,9 @@ public:
bool isBaseVertexSupported() const;
bool isMultiFormatMRTSupported() const;
bool isCopyBufferSupported() const;
bool isCopyBufferToTextureSupported() const;
bool isCopyTextureToBufferSupported() const;
bool isCopyRenderTargetToBufferSupported() const;
/**
* Returns the maximum supported width or height of a texture.
+70
View File
@@ -22,6 +22,7 @@
#include "graphics/Graphics.h"
#include "Graphics.h"
#include "Buffer.h"
#include "common/int.h"
// STD
@@ -524,6 +525,75 @@ void Texture::readbackImageData(love::image::ImageData *data, int slice, int mip
gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, current_fbo);
}
void Texture::copyFromBuffer(love::graphics::Buffer *source, size_t sourceoffset, int sourcewidth, size_t size, int slice, int mipmap, const Rect &rect)
{
// Higher level code does validation.
GLuint glbuffer = (GLuint) source->getHandle();
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, glbuffer);
if (!isCompressed()) // Not supported in GL with compressed textures...
glPixelStorei(GL_UNPACK_ROW_LENGTH, sourcewidth);
// glTexSubImage and friends copy from the active pixel_unpack_buffer by
// treating the pointer as a byte offset.
const uint8 *byteoffset = (const uint8 *)(ptrdiff_t)sourceoffset;
uploadByteData(format, byteoffset, size, mipmap, slice, rect);
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
}
void Texture::copyToBuffer(love::graphics::Buffer *dest, int slice, int mipmap, const Rect &rect, size_t destoffset, int destwidth, size_t size)
{
// Higher level code does validation.
GLuint glbuffer = (GLuint) dest->getHandle();
glBindBuffer(GL_PIXEL_PACK_BUFFER, glbuffer);
if (!isCompressed()) // Not supported in GL with compressed textures...
glPixelStorei(GL_PACK_ROW_LENGTH, destwidth);
gl.bindTextureToUnit(this, 0, false);
bool isSRGB = false;
OpenGL::TextureFormat fmt = gl.convertPixelFormat(format, false, isSRGB);
// glTexSubImage and friends copy from the active pixel_unpack_buffer by
// treating the pointer as a byte offset.
uint8 *byteoffset = (uint8 *)(ptrdiff_t)destoffset;
if (GLAD_VERSION_4_5)
{
if (isCompressed())
glGetCompressedTextureSubImage(texture, mipmap, rect.x, rect.y, slice, rect.w, rect.h, 1, size, byteoffset);
else
glGetTextureSubImage(texture, mipmap, rect.x, rect.y, slice, rect.w, rect.h, 1, fmt.externalformat, fmt.type, size, byteoffset);
}
else if (fbo)
{
GLuint current_fbo = gl.getFramebuffer(OpenGL::FRAMEBUFFER_ALL);
gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, getFBO());
if (slice > 0 || mipmap > 0)
{
int layer = texType == TEXTURE_CUBE ? 0 : slice;
int face = texType == TEXTURE_CUBE ? slice : 0;
gl.framebufferTexture(GL_COLOR_ATTACHMENT0, texType, texture, mipmap, layer, face);
}
glReadPixels(rect.x, rect.y, rect.w, rect.h, fmt.externalformat, fmt.type, byteoffset);
if (slice > 0 || mipmap > 0)
gl.framebufferTexture(GL_COLOR_ATTACHMENT0, texType, texture, 0, 0, 0);
gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, current_fbo);
}
glPixelStorei(GL_PACK_ROW_LENGTH, 0);
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
}
void Texture::setSamplerState(const SamplerState &s)
{
if (s.depthSampleMode.hasValue && !gl.isDepthCompareSampleSupported())
+3
View File
@@ -46,6 +46,9 @@ public:
bool loadVolatile() override;
void unloadVolatile() override;
void copyFromBuffer(love::graphics::Buffer *source, size_t sourceoffset, int sourcewidth, size_t size, int slice, int mipmap, const Rect &rect) override;
void copyToBuffer(love::graphics::Buffer *dest, int slice, int mipmap, const Rect &rect, size_t destoffset, int destwidth, size_t size) override;
void setSamplerState(const SamplerState &s) override;
ptrdiff_t getHandle() const override;
+66
View File
@@ -3353,6 +3353,70 @@ int w_copyBuffer(lua_State *L)
return 0;
}
int w_copyBufferToTexture(lua_State *L)
{
Buffer *source = luax_checkbuffer(L, 1);
Texture *dest = luax_checktexture(L, 2);
ptrdiff_t sourceoffset = luaL_optinteger(L, 3, 0);
if (sourceoffset < 0)
return luaL_error(L, "copyBufferToTexture source offset cannot be negative.");
int sourcewidth = (int) luaL_optinteger(L, 4, 0);
int slice = 0;
int mipmap = 0;
if (dest->getTextureType() != TEXTURE_2D)
slice = (int) luaL_checkinteger(L, 5) - 1;
mipmap = (int) luaL_optinteger(L, 6, 1) - 1;
Rect rect = {0, 0, dest->getPixelWidth(mipmap), dest->getPixelHeight(mipmap)};
if (!lua_isnoneornil(L, 7))
{
rect.x = (int) luaL_checkinteger(L, 7);
rect.y = (int) luaL_checkinteger(L, 8);
rect.w = (int) luaL_checkinteger(L, 9);
rect.h = (int) luaL_checkinteger(L, 10);
}
luax_catchexcept(L, [&](){ instance()->copyBufferToTexture(source, dest, sourceoffset, sourcewidth, slice, mipmap, rect); });
return 0;
}
int w_copyTextureToBuffer(lua_State *L)
{
Texture *source = luax_checktexture(L, 1);
Buffer *dest = luax_checkbuffer(L, 2);
int slice = 0;
int mipmap = 0;
if (source->getTextureType() != TEXTURE_2D)
slice = (int) luaL_checkinteger(L, 3) - 1;
mipmap = (int) luaL_optinteger(L, 4, 1) - 1;
Rect rect = {0, 0, source->getPixelWidth(mipmap), source->getPixelHeight(mipmap)};
if (!lua_isnoneornil(L, 5))
{
rect.x = (int) luaL_checkinteger(L, 5);
rect.y = (int) luaL_checkinteger(L, 6);
rect.w = (int) luaL_checkinteger(L, 7);
rect.h = (int) luaL_checkinteger(L, 8);
}
ptrdiff_t destoffset = luaL_optinteger(L, 9, 0);
if (destoffset < 0)
return luaL_error(L, "copyTextureToBuffer dest offset cannot be negative.");
int destwidth = (int) luaL_optinteger(L, 10, 0);
luax_catchexcept(L, [&](){ instance()->copyTextureToBuffer(source, dest, slice, mipmap, rect, destoffset, destwidth); });
return 0;
}
int w_flushBatch(lua_State *)
{
instance()->flushBatchedDraws();
@@ -3553,6 +3617,8 @@ static const luaL_Reg functions[] =
{ "dispatchThreadgroups", w_dispatchThreadgroups },
{ "copyBuffer", w_copyBuffer },
{ "copyBufferToTexture", w_copyBufferToTexture },
{ "copyTextureToBuffer", w_copyTextureToBuffer },
{ "isCreated", w_isCreated },
{ "isActive", w_isActive },
+1 -1
View File
@@ -390,13 +390,13 @@ int w_Texture_newImageData(lua_State *L)
int slice = 0;
int mipmap = 0;
Rect rect = {0, 0, t->getPixelWidth(), t->getPixelHeight()};
if (t->getTextureType() != TEXTURE_2D)
slice = (int) luaL_checkinteger(L, 2) - 1;
mipmap = (int) luaL_optinteger(L, 3, 1) - 1;
Rect rect = {0, 0, t->getPixelWidth(mipmap), t->getPixelHeight(mipmap)};
if (!lua_isnoneornil(L, 4))
{
rect.x = (int) luaL_checkinteger(L, 4);