mirror of
https://github.com/love2d/love.git
synced 2026-08-16 08:11:02 +02:00
Add sync and async texture and buffer readback APIs.
- Add imagedata = love.graphics.readbackTexture(texture, [slice, mipmap, x, y, w, h, [dest, destx, desty]]). - Add readback = love.graphics.readbackTextureAsync(texture, [slice, mipmap, x, y, w, h, [dest, destx, desty]]). - Add bytedata = love.graphics.readbackBuffer(buffer, [offset, size, [dest, destoffset]]). - Add readback = ove.graphics.readbackBufferAsync(buffer, [offset, size, [dest, destoffset]]). - Deprecate Texture:newImageData. The async variants return a new GraphicsReadback object. It has the following methods: - isComplete() - hasError() - wait() - getBufferData() - getImageData() - update() (called automatically every frame by love, not normally needed). Support notes: - readbackBuffer and readbackBufferAsync require buffer copying support. - readbackTexture with a render target (canvas) is always supported. readbackTexture with a non-render-target requires copy-texture-to-buffer support. - readbackTextureAsync with a render target requires copy-render-target-to-buffer support. readbackTextureAsync with a non-render-target requires copy-texture-to-buffer support.
This commit is contained in:
@@ -45,6 +45,22 @@ bool FenceSync::fence()
|
||||
return !wasActive;
|
||||
}
|
||||
|
||||
bool FenceSync::isComplete() const
|
||||
{
|
||||
if (sync == 0)
|
||||
return true;
|
||||
|
||||
GLenum status = glClientWaitSync(sync, 0, 0);
|
||||
|
||||
if (status == GL_ALREADY_SIGNALED || status == GL_CONDITION_SATISFIED)
|
||||
return true;
|
||||
|
||||
if (status == GL_WAIT_FAILED)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FenceSync::cpuWait()
|
||||
{
|
||||
if (sync == 0)
|
||||
|
||||
@@ -42,6 +42,7 @@ public:
|
||||
~FenceSync();
|
||||
|
||||
bool fence();
|
||||
bool isComplete() const;
|
||||
bool cpuWait();
|
||||
void cleanup();
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include "Graphics.h"
|
||||
#include "font/Font.h"
|
||||
#include "StreamBuffer.h"
|
||||
#include "GraphicsReadback.h"
|
||||
#include "math/MathModule.h"
|
||||
#include "window/Window.h"
|
||||
#include "Buffer.h"
|
||||
@@ -177,6 +178,16 @@ love::graphics::Buffer *Graphics::newBuffer(const Buffer::Settings &settings, co
|
||||
return new Buffer(this, settings, format, data, size, arraylength);
|
||||
}
|
||||
|
||||
love::graphics::GraphicsReadback *Graphics::newReadbackInternal(ReadbackMethod method, love::graphics::Buffer *buffer, size_t offset, size_t size, data::ByteData *dest, size_t destoffset)
|
||||
{
|
||||
return new GraphicsReadback(this, method, buffer, offset, size, dest, destoffset);
|
||||
}
|
||||
|
||||
love::graphics::GraphicsReadback *Graphics::newReadbackInternal(ReadbackMethod method, love::graphics::Texture *texture, int slice, int mipmap, const Rect &rect, image::ImageData *dest, int destx, int desty)
|
||||
{
|
||||
return new GraphicsReadback(this, method, texture, slice, mipmap, rect, dest, destx, desty);
|
||||
}
|
||||
|
||||
Matrix4 Graphics::computeDeviceProjection(const Matrix4 &projection, bool rendertotexture) const
|
||||
{
|
||||
uint32 flags = DEVICE_PROJECTION_DEFAULT;
|
||||
@@ -1333,6 +1344,7 @@ void Graphics::present(void *screenshotCallbackData)
|
||||
renderTargetSwitchCount = 0;
|
||||
drawCallsBatched = 0;
|
||||
|
||||
updatePendingReadbacks();
|
||||
updateTemporaryResources();
|
||||
}
|
||||
|
||||
|
||||
@@ -141,6 +141,10 @@ private:
|
||||
love::graphics::ShaderStage *newShaderStageInternal(ShaderStageType stage, const std::string &cachekey, const std::string &source, bool gles) override;
|
||||
love::graphics::Shader *newShaderInternal(StrongRef<love::graphics::ShaderStage> stages[SHADERSTAGE_MAX_ENUM]) override;
|
||||
love::graphics::StreamBuffer *newStreamBuffer(BufferUsage type, size_t size) override;
|
||||
|
||||
love::graphics::GraphicsReadback *newReadbackInternal(ReadbackMethod method, love::graphics::Buffer *buffer, size_t offset, size_t size, data::ByteData *dest, size_t destoffset) override;
|
||||
love::graphics::GraphicsReadback *newReadbackInternal(ReadbackMethod method, love::graphics::Texture *texture, int slice, int mipmap, const Rect &rect, image::ImageData *dest, int destx, int desty) override;
|
||||
|
||||
void setRenderTargetsInternal(const RenderTargets &rts, int pixelw, int pixelh, bool hasSRGBtexture) override;
|
||||
void initCapabilities() override;
|
||||
void getAPIStats(int &shaderswitches) const override;
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
/**
|
||||
* Copyright (c) 2006-2022 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 "GraphicsReadback.h"
|
||||
#include "Buffer.h"
|
||||
#include "Texture.h"
|
||||
#include "graphics/Graphics.h"
|
||||
#include "data/ByteData.h"
|
||||
|
||||
namespace love
|
||||
{
|
||||
namespace graphics
|
||||
{
|
||||
namespace opengl
|
||||
{
|
||||
|
||||
GraphicsReadback::GraphicsReadback(love::graphics::Graphics *gfx, ReadbackMethod method, love::graphics::Buffer *buffer, size_t offset, size_t size, data::ByteData *dest, size_t destoffset)
|
||||
: love::graphics::GraphicsReadback(gfx, method, buffer, offset, size, dest, destoffset)
|
||||
{
|
||||
// Immediate readback of readback-type buffers doesn't need a staging buffer.
|
||||
if (method != READBACK_IMMEDIATE || buffer->getDataUsage() != BUFFERDATAUSAGE_READBACK)
|
||||
{
|
||||
stagingBuffer = gfx->getTemporaryBuffer(size, DATAFORMAT_FLOAT, 0, BUFFERDATAUSAGE_READBACK);
|
||||
gfx->copyBuffer(buffer, stagingBuffer, offset, 0, size);
|
||||
}
|
||||
|
||||
if (method == READBACK_IMMEDIATE)
|
||||
{
|
||||
if (stagingBuffer.get())
|
||||
{
|
||||
status = readbackBuffer(stagingBuffer, 0, size);
|
||||
gfx->releaseTemporaryBuffer(stagingBuffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
status = readbackBuffer(buffer, offset, size);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sync.fence();
|
||||
}
|
||||
}
|
||||
|
||||
GraphicsReadback::GraphicsReadback(love::graphics::Graphics *gfx, ReadbackMethod method, love::graphics::Texture *texture, int slice, int mipmap, const Rect &rect, image::ImageData *dest, int destx, int desty)
|
||||
: love::graphics::GraphicsReadback(gfx, method, texture, slice, mipmap, rect, dest, destx, desty)
|
||||
{
|
||||
size_t size = getPixelFormatSliceSize(textureFormat, rect.w, rect.h);
|
||||
|
||||
if (method == READBACK_IMMEDIATE)
|
||||
{
|
||||
void *dest = prepareReadbackDest(size);
|
||||
|
||||
love::thread::Lock lock(imageData->getMutex());
|
||||
|
||||
// Direct readback without copying avoids the need for a staging buffer,
|
||||
// and lowers the system requirements of immediate RT readback.
|
||||
Texture *t = (Texture *) texture;
|
||||
t->readbackInternal(slice, mipmap, rect, imageData->getWidth(), size, dest);
|
||||
|
||||
status = STATUS_COMPLETE;
|
||||
}
|
||||
else
|
||||
{
|
||||
stagingBuffer = gfx->getTemporaryBuffer(size, DATAFORMAT_FLOAT, 0, BUFFERDATAUSAGE_READBACK);
|
||||
|
||||
gfx->copyTextureToBuffer(texture, stagingBuffer, slice, mipmap, rect, 0, 0);
|
||||
sync.fence();
|
||||
}
|
||||
}
|
||||
|
||||
GraphicsReadback::~GraphicsReadback()
|
||||
{
|
||||
}
|
||||
|
||||
void GraphicsReadback::wait()
|
||||
{
|
||||
if (status != STATUS_WAITING)
|
||||
return;
|
||||
|
||||
sync.cpuWait();
|
||||
update();
|
||||
}
|
||||
|
||||
void GraphicsReadback::update()
|
||||
{
|
||||
if (status != STATUS_WAITING)
|
||||
return;
|
||||
|
||||
if (sync.isComplete())
|
||||
{
|
||||
if (stagingBuffer.get())
|
||||
status = readbackBuffer(stagingBuffer, 0, stagingBuffer->getSize());
|
||||
else
|
||||
status = STATUS_ERROR;
|
||||
|
||||
if (stagingBuffer.get())
|
||||
{
|
||||
auto gfx = Module::getInstance<love::graphics::Graphics>(Module::M_GRAPHICS);
|
||||
if (gfx != nullptr)
|
||||
gfx->releaseTemporaryBuffer(stagingBuffer);
|
||||
stagingBuffer.set(nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // opengl
|
||||
} // graphics
|
||||
} // love
|
||||
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* Copyright (c) 2006-2022 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/GraphicsReadback.h"
|
||||
#include "FenceSync.h"
|
||||
#include "common/math.h"
|
||||
|
||||
namespace love
|
||||
{
|
||||
namespace graphics
|
||||
{
|
||||
namespace opengl
|
||||
{
|
||||
|
||||
class GraphicsReadback final : public love::graphics::GraphicsReadback
|
||||
{
|
||||
public:
|
||||
|
||||
GraphicsReadback(love::graphics::Graphics *gfx, ReadbackMethod method, love::graphics::Buffer *buffer, size_t offset, size_t size, data::ByteData *dest, size_t destoffset);
|
||||
GraphicsReadback(love::graphics::Graphics *gfx, ReadbackMethod method, love::graphics::Texture *texture, int slice, int mipmap, const Rect &rect, image::ImageData *dest, int destx, int desty);
|
||||
virtual ~GraphicsReadback();
|
||||
|
||||
void wait() override;
|
||||
void update() override;
|
||||
|
||||
private:
|
||||
|
||||
FenceSync sync;
|
||||
|
||||
StrongRef<love::graphics::Buffer> stagingBuffer;
|
||||
|
||||
}; // GraphicsReadback
|
||||
|
||||
} // opengl
|
||||
} // graphics
|
||||
} // love
|
||||
@@ -506,30 +506,46 @@ void Texture::generateMipmapsInternal()
|
||||
glGenerateMipmap(gltextype);
|
||||
}
|
||||
|
||||
void Texture::readbackImageData(love::image::ImageData *data, int slice, int mipmap, const Rect &r)
|
||||
void Texture::readbackInternal(int slice, int mipmap, const Rect &rect, int destwidth, size_t size, void *dest)
|
||||
{
|
||||
if (fbo == 0) // Should never be reached.
|
||||
return;
|
||||
// Not supported in GL with compressed textures...
|
||||
if ((GLAD_VERSION_1_1 || GLAD_ES_VERSION_3_0) && !isCompressed())
|
||||
glPixelStorei(GL_PACK_ROW_LENGTH, destwidth);
|
||||
|
||||
gl.bindTextureToUnit(this, 0, false);
|
||||
|
||||
bool isSRGB = false;
|
||||
OpenGL::TextureFormat fmt = gl.convertPixelFormat(data->getFormat(), false, isSRGB);
|
||||
OpenGL::TextureFormat fmt = gl.convertPixelFormat(format, false, isSRGB);
|
||||
|
||||
GLuint current_fbo = gl.getFramebuffer(OpenGL::FRAMEBUFFER_ALL);
|
||||
gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, getFBO());
|
||||
|
||||
if (slice > 0 || mipmap > 0)
|
||||
if (gl.isCopyTextureToBufferSupported())
|
||||
{
|
||||
int layer = texType == TEXTURE_CUBE ? 0 : slice;
|
||||
int face = texType == TEXTURE_CUBE ? slice : 0;
|
||||
gl.framebufferTexture(GL_COLOR_ATTACHMENT0, texType, texture, mipmap, layer, face);
|
||||
if (isCompressed())
|
||||
glGetCompressedTextureSubImage(texture, mipmap, rect.x, rect.y, slice, rect.w, rect.h, 1, size, dest);
|
||||
else
|
||||
glGetTextureSubImage(texture, mipmap, rect.x, rect.y, slice, rect.w, rect.h, 1, fmt.externalformat, fmt.type, size, dest);
|
||||
}
|
||||
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, dest);
|
||||
|
||||
if (slice > 0 || mipmap > 0)
|
||||
gl.framebufferTexture(GL_COLOR_ATTACHMENT0, texType, texture, 0, 0, 0);
|
||||
|
||||
gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, current_fbo);
|
||||
}
|
||||
|
||||
glReadPixels(r.x, r.y, r.w, r.h, fmt.externalformat, fmt.type, data->getData());
|
||||
|
||||
if (slice > 0 || mipmap > 0)
|
||||
gl.framebufferTexture(GL_COLOR_ATTACHMENT0, texType, texture, 0, 0, 0);
|
||||
|
||||
gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, current_fbo);
|
||||
if ((GLAD_VERSION_1_1 || GLAD_ES_VERSION_3_0) && !isCompressed())
|
||||
glPixelStorei(GL_PACK_ROW_LENGTH, 0);
|
||||
}
|
||||
|
||||
void Texture::copyFromBuffer(love::graphics::Buffer *source, size_t sourceoffset, int sourcewidth, size_t size, int slice, int mipmap, const Rect &rect)
|
||||
@@ -558,46 +574,12 @@ void Texture::copyToBuffer(love::graphics::Buffer *dest, int slice, int mipmap,
|
||||
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
|
||||
// glTexSubImage and friends copy to the active PIXEL_PACK_BUFFER by
|
||||
// treating the pointer as a byte offset.
|
||||
uint8 *byteoffset = (uint8 *)(ptrdiff_t)destoffset;
|
||||
|
||||
if (gl.isCopyTextureToBufferSupported())
|
||||
{
|
||||
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());
|
||||
readbackInternal(slice, mipmap, rect, destwidth, size, byteoffset);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -58,6 +58,8 @@ public:
|
||||
|
||||
inline GLuint getFBO() const { return fbo; }
|
||||
|
||||
void readbackInternal(int slice, int mipmap, const Rect &rect, int destwidth, size_t size, void *dest);
|
||||
|
||||
private:
|
||||
|
||||
void createTexture();
|
||||
@@ -66,8 +68,6 @@ private:
|
||||
|
||||
void generateMipmapsInternal() override;
|
||||
|
||||
void readbackImageData(love::image::ImageData *imagedata, int slice, int mipmap, const Rect &rect) override;
|
||||
|
||||
Slices slices;
|
||||
|
||||
GLuint fbo;
|
||||
|
||||
Reference in New Issue
Block a user