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:
Alex Szpakowski
2022-05-30 21:53:55 -03:00
parent fb450b1df4
commit 3a5c3df02f
27 changed files with 1186 additions and 152 deletions
+4
View File
@@ -199,6 +199,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 usage, 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 hasSRGBcanvas) override;
void initCapabilities() override;
void getAPIStats(int &shaderswitches) const override;
+12
View File
@@ -22,6 +22,7 @@
#include "StreamBuffer.h"
#include "Buffer.h"
#include "Texture.h"
#include "GraphicsReadback.h"
#include "Shader.h"
#include "ShaderStage.h"
#include "window/Window.h"
@@ -418,6 +419,16 @@ love::graphics::Buffer *Graphics::newBuffer(const Buffer::Settings &settings, co
return new Buffer(this, device, 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_FLIP_Y;
@@ -1617,6 +1628,7 @@ void Graphics::present(void *screenshotCallbackData)
renderTargetSwitchCount = 0;
drawCallsBatched = 0;
updatePendingReadbacks();
updateTemporaryResources();
}}
@@ -0,0 +1,54 @@
/**
* 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 "common/math.h"
#include <atomic>
#import <Metal/MTLCommandBuffer.h>
namespace love::graphics::metal
{
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:
id<MTLCommandBuffer> cmd;
std::atomic_bool done;
StrongRef<love::graphics::Buffer> stagingBuffer;
}; // GraphicsReadback
} // love::graphics::metal
@@ -0,0 +1,143 @@
/**
* 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.h"
#include "data/ByteData.h"
namespace love::graphics::metal
{
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)
, done(false)
{ @autoreleasepool {
auto mgfx = (Graphics *) gfx;
// 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);
}
// use instead of get, in case this was the first command in the frame.
cmd = mgfx->useCommandBuffer();
auto pthis = this;
pthis->retain();
[cmd addCompletedHandler:^(id<MTLCommandBuffer> _Nonnull)
{
pthis->done = true;
pthis->release();
}];
if (method == READBACK_IMMEDIATE)
{
wait();
if (stagingBuffer.get())
{
status = readbackBuffer(stagingBuffer, 0, size);
gfx->releaseTemporaryBuffer(stagingBuffer);
}
else
{
status = readbackBuffer(buffer, offset, size);
}
}
}}
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)
, done(false)
{ @autoreleasepool {
auto mgfx = (Graphics *) gfx;
size_t size = getPixelFormatSliceSize(textureFormat, rect.w, rect.h);
stagingBuffer = gfx->getTemporaryBuffer(size, DATAFORMAT_FLOAT, 0, BUFFERDATAUSAGE_READBACK);
gfx->copyTextureToBuffer(texture, stagingBuffer, slice, mipmap, rect, 0, 0);
cmd = mgfx->getCommandBuffer();
auto pthis = this;
pthis->retain();
[cmd addCompletedHandler:^(id<MTLCommandBuffer> _Nonnull)
{
pthis->done = true;
pthis->release();
}];
if (method == READBACK_IMMEDIATE)
{
wait();
status = readbackBuffer(stagingBuffer, 0, size);
gfx->releaseTemporaryBuffer(stagingBuffer);
}
}}
GraphicsReadback::~GraphicsReadback()
{ @autoreleasepool {
cmd = nil;
}}
void GraphicsReadback::wait()
{ @autoreleasepool {
if (status != STATUS_WAITING || cmd == nil)
return;
if (cmd.status == MTLCommandBufferStatusNotEnqueued)
{
auto gfx = Graphics::getInstance();
gfx->submitCommandBuffer(Graphics::SUBMIT_STORE);
}
[cmd waitUntilCompleted];
cmd = nil;
update();
}}
void GraphicsReadback::update()
{
if (status != STATUS_WAITING)
return;
if (done)
{
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);
}
}
}
} // love::graphics::metal
-1
View File
@@ -57,7 +57,6 @@ private:
void uploadByteData(PixelFormat pixelformat, const void *data, size_t size, int level, int slice, const Rect &r) override;
void generateMipmapsInternal() override;
void readbackImageData(love::image::ImageData *imagedata, int slice, int mipmap, const Rect &rect) override;
id<MTLTexture> texture;
id<MTLTexture> msaaTexture;
-46
View File
@@ -273,52 +273,6 @@ void Texture::generateMipmapsInternal()
[encoder generateMipmapsForTexture:texture];
}}
void Texture::readbackImageData(love::image::ImageData *imagedata, int slice, int mipmap, const Rect &rect)
{ @autoreleasepool {
auto gfx = Graphics::getInstance();
id<MTLBlitCommandEncoder> encoder = gfx->useBlitEncoder();
size_t rowSize = 0;
if (isCompressed())
rowSize = getPixelFormatCompressedBlockRowSize(format, rect.w);
else
rowSize = getPixelFormatUncompressedRowSize(format, rect.w);
// TODO: Verify this is correct for compressed formats at small sizes.
// TODO: make sure this is consistent with the imagedata byte size?
size_t sliceSize = getPixelFormatSliceSize(format, rect.w, rect.h);
int z = texType == TEXTURE_VOLUME ? slice : 0;
id<MTLBuffer> buffer = [gfx->device newBufferWithLength:sliceSize
options:MTLResourceStorageModeShared];
MTLBlitOption options = MTLBlitOptionNone;
if (isPixelFormatDepthStencil(format))
options = MTLBlitOptionDepthFromDepthStencil;
[encoder copyFromTexture:texture
sourceSlice:texType == TEXTURE_VOLUME ? 0 : slice
sourceLevel:mipmap
sourceOrigin:MTLOriginMake(rect.x, rect.y, z)
sourceSize:MTLSizeMake(rect.w, rect.h, 1)
toBuffer:buffer
destinationOffset:0
destinationBytesPerRow:rowSize
destinationBytesPerImage:sliceSize
options:options];
id<MTLCommandBuffer> cmd = gfx->getCommandBuffer();
gfx->submitBlitEncoder();
gfx->submitCommandBuffer(Graphics::SUBMIT_STORE);
[cmd waitUntilCompleted];
memcpy(imagedata->getData(), buffer.contents, imagedata->getSize());
}}
void Texture::copyFromBuffer(love::graphics::Buffer *source, size_t sourceoffset, int sourcewidth, size_t size, int slice, int mipmap, const Rect &rect)
{ @autoreleasepool {
id<MTLBlitCommandEncoder> encoder = Graphics::getInstance()->useBlitEncoder();