From 3a5c3df02f6091297481bfca34c8bf793f8d3cef Mon Sep 17 00:00:00 2001 From: Alex Szpakowski Date: Mon, 30 May 2022 21:53:55 -0300 Subject: [PATCH] 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. --- CMakeLists.txt | 6 + .../xcode/liblove.xcodeproj/project.pbxproj | 36 +++ src/modules/graphics/Graphics.cpp | 55 +++++ src/modules/graphics/Graphics.h | 13 + src/modules/graphics/GraphicsReadback.cpp | 229 ++++++++++++++++++ src/modules/graphics/GraphicsReadback.h | 112 +++++++++ src/modules/graphics/Texture.cpp | 41 ---- src/modules/graphics/Texture.h | 3 - src/modules/graphics/metal/Graphics.h | 4 + src/modules/graphics/metal/Graphics.mm | 12 + src/modules/graphics/metal/GraphicsReadback.h | 54 +++++ .../graphics/metal/GraphicsReadback.mm | 143 +++++++++++ src/modules/graphics/metal/Texture.h | 1 - src/modules/graphics/metal/Texture.mm | 46 ---- src/modules/graphics/opengl/FenceSync.cpp | 16 ++ src/modules/graphics/opengl/FenceSync.h | 1 + src/modules/graphics/opengl/Graphics.cpp | 12 + src/modules/graphics/opengl/Graphics.h | 4 + .../graphics/opengl/GraphicsReadback.cpp | 126 ++++++++++ .../graphics/opengl/GraphicsReadback.h | 56 +++++ src/modules/graphics/opengl/Texture.cpp | 88 +++---- src/modules/graphics/opengl/Texture.h | 4 +- src/modules/graphics/wrap_Graphics.cpp | 126 ++++++++++ src/modules/graphics/wrap_Graphics.h | 1 + .../graphics/wrap_GraphicsReadback.cpp | 95 ++++++++ src/modules/graphics/wrap_GraphicsReadback.h | 36 +++ src/modules/graphics/wrap_Texture.cpp | 18 +- 27 files changed, 1186 insertions(+), 152 deletions(-) create mode 100644 src/modules/graphics/GraphicsReadback.cpp create mode 100644 src/modules/graphics/GraphicsReadback.h create mode 100644 src/modules/graphics/metal/GraphicsReadback.h create mode 100644 src/modules/graphics/metal/GraphicsReadback.mm create mode 100644 src/modules/graphics/opengl/GraphicsReadback.cpp create mode 100644 src/modules/graphics/opengl/GraphicsReadback.h create mode 100644 src/modules/graphics/wrap_GraphicsReadback.cpp create mode 100644 src/modules/graphics/wrap_GraphicsReadback.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 399676783..959e32d91 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -512,6 +512,8 @@ set(LOVE_SRC_MODULE_GRAPHICS_ROOT src/modules/graphics/Font.h src/modules/graphics/Graphics.cpp src/modules/graphics/Graphics.h + src/modules/graphics/GraphicsReadback.cpp + src/modules/graphics/GraphicsReadback.h src/modules/graphics/Mesh.cpp src/modules/graphics/Mesh.h src/modules/graphics/ParticleSystem.cpp @@ -547,6 +549,8 @@ set(LOVE_SRC_MODULE_GRAPHICS_ROOT src/modules/graphics/wrap_Font.h src/modules/graphics/wrap_Graphics.cpp src/modules/graphics/wrap_Graphics.h + src/modules/graphics/wrap_GraphicsReadback.cpp + src/modules/graphics/wrap_GraphicsReadback.h src/modules/graphics/wrap_Mesh.cpp src/modules/graphics/wrap_Mesh.h src/modules/graphics/wrap_ParticleSystem.cpp @@ -572,6 +576,8 @@ set(LOVE_SRC_MODULE_GRAPHICS_OPENGL src/modules/graphics/opengl/FenceSync.h src/modules/graphics/opengl/Graphics.cpp src/modules/graphics/opengl/Graphics.h + src/modules/graphics/opengl/GraphicsReadback.cpp + src/modules/graphics/opengl/GraphicsReadback.h src/modules/graphics/opengl/OpenGL.cpp src/modules/graphics/opengl/OpenGL.h src/modules/graphics/opengl/Shader.cpp diff --git a/platform/xcode/liblove.xcodeproj/project.pbxproj b/platform/xcode/liblove.xcodeproj/project.pbxproj index a4b8df827..c1aa7a6d8 100644 --- a/platform/xcode/liblove.xcodeproj/project.pbxproj +++ b/platform/xcode/liblove.xcodeproj/project.pbxproj @@ -826,12 +826,22 @@ FA6BDF8E281219E900240F2A /* DataStream.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA6BDF8C281219E900240F2A /* DataStream.cpp */; }; FA6BDF8F281219E900240F2A /* DataStream.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA6BDF8C281219E900240F2A /* DataStream.cpp */; }; FA6BDF90281219E900240F2A /* DataStream.h in Headers */ = {isa = PBXBuildFile; fileRef = FA6BDF8D281219E900240F2A /* DataStream.h */; }; + FA6BDF89280B62A000240F2A /* GraphicsReadback.mm in Sources */ = {isa = PBXBuildFile; fileRef = FA6BDF88280B62A000240F2A /* GraphicsReadback.mm */; }; + FA6BDF8A280B62A000240F2A /* GraphicsReadback.mm in Sources */ = {isa = PBXBuildFile; fileRef = FA6BDF88280B62A000240F2A /* GraphicsReadback.mm */; }; FA76344A1E28722A0066EF9E /* StreamBuffer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA7634481E28722A0066EF9E /* StreamBuffer.cpp */; }; FA76344B1E28722A0066EF9E /* StreamBuffer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA7634481E28722A0066EF9E /* StreamBuffer.cpp */; }; FA76344C1E28722A0066EF9E /* StreamBuffer.h in Headers */ = {isa = PBXBuildFile; fileRef = FA7634491E28722A0066EF9E /* StreamBuffer.h */; }; FA7E9207277E120900C24CB2 /* theora.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA7E9206277E120900C24CB2 /* theora.xcframework */; }; FA84DE612778D7F3002674C6 /* SpirvIntrinsics.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA84DE602778D7F3002674C6 /* SpirvIntrinsics.cpp */; }; FA84DE622778D7F3002674C6 /* SpirvIntrinsics.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA84DE602778D7F3002674C6 /* SpirvIntrinsics.cpp */; }; + FA84DE6627791C36002674C6 /* GraphicsReadback.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA84DE6427791C36002674C6 /* GraphicsReadback.cpp */; }; + FA84DE6727791C36002674C6 /* GraphicsReadback.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA84DE6427791C36002674C6 /* GraphicsReadback.cpp */; }; + FA84DE6827791C36002674C6 /* GraphicsReadback.h in Headers */ = {isa = PBXBuildFile; fileRef = FA84DE6527791C36002674C6 /* GraphicsReadback.h */; }; + FA84DE6B277943F6002674C6 /* GraphicsReadback.h in Headers */ = {isa = PBXBuildFile; fileRef = FA84DE69277943F6002674C6 /* GraphicsReadback.h */; }; + FA84DE6C277943F6002674C6 /* GraphicsReadback.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA84DE6A277943F6002674C6 /* GraphicsReadback.cpp */; }; + FA84DE6D277943F6002674C6 /* GraphicsReadback.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA84DE6A277943F6002674C6 /* GraphicsReadback.cpp */; }; + FA84DE7127795E22002674C6 /* wrap_GraphicsReadback.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA84DE6F27795E22002674C6 /* wrap_GraphicsReadback.cpp */; }; + FA84DE7227795E22002674C6 /* wrap_GraphicsReadback.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA84DE6F27795E22002674C6 /* wrap_GraphicsReadback.cpp */; }; FA84DE76277CB3D5002674C6 /* SDL2.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA84DE75277CB3D4002674C6 /* SDL2.xcframework */; }; FA84DE7A277D4C88002674C6 /* modplug.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA84DE79277D4C88002674C6 /* modplug.xcframework */; }; FA84DE7C277E045E002674C6 /* ogg.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA84DE7B277E045E002674C6 /* ogg.xcframework */; }; @@ -1909,6 +1919,8 @@ FA6BDE5B1F31725300786805 /* Color.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Color.h; sourceTree = ""; }; FA6BDF8C281219E900240F2A /* DataStream.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DataStream.cpp; sourceTree = ""; }; FA6BDF8D281219E900240F2A /* DataStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DataStream.h; sourceTree = ""; }; + FA6BDF88280B62A000240F2A /* GraphicsReadback.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = GraphicsReadback.mm; sourceTree = ""; }; + FA6BDF8B280B62B600240F2A /* GraphicsReadback.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GraphicsReadback.h; sourceTree = ""; }; FA7634481E28722A0066EF9E /* StreamBuffer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StreamBuffer.cpp; sourceTree = ""; }; FA7634491E28722A0066EF9E /* StreamBuffer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StreamBuffer.h; sourceTree = ""; }; FA7DA04C1C16874A0056B200 /* wrap_Math.lua */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = wrap_Math.lua; sourceTree = ""; }; @@ -1917,6 +1929,12 @@ FA84DE5E2778D7DC002674C6 /* glslang_c_interface.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = glslang_c_interface.h; sourceTree = ""; }; FA84DE5F2778D7DC002674C6 /* glslang_c_shader_types.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = glslang_c_shader_types.h; sourceTree = ""; }; FA84DE602778D7F3002674C6 /* SpirvIntrinsics.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SpirvIntrinsics.cpp; sourceTree = ""; }; + FA84DE6427791C36002674C6 /* GraphicsReadback.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = GraphicsReadback.cpp; sourceTree = ""; }; + FA84DE6527791C36002674C6 /* GraphicsReadback.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GraphicsReadback.h; sourceTree = ""; }; + FA84DE69277943F6002674C6 /* GraphicsReadback.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GraphicsReadback.h; sourceTree = ""; }; + FA84DE6A277943F6002674C6 /* GraphicsReadback.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = GraphicsReadback.cpp; sourceTree = ""; }; + FA84DE6E27795E22002674C6 /* wrap_GraphicsReadback.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wrap_GraphicsReadback.h; sourceTree = ""; }; + FA84DE6F27795E22002674C6 /* wrap_GraphicsReadback.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = wrap_GraphicsReadback.cpp; sourceTree = ""; }; FA84DE75277CB3D4002674C6 /* SDL2.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = SDL2.xcframework; path = ios/libraries/SDL2.xcframework; sourceTree = ""; }; FA84DE79277D4C88002674C6 /* modplug.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = modplug.xcframework; path = ios/libraries/modplug.xcframework; sourceTree = ""; }; FA84DE7B277E045E002674C6 /* ogg.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = ogg.xcframework; path = ios/libraries/ogg.xcframework; sourceTree = ""; }; @@ -2819,6 +2837,8 @@ FA1BA09C1E16CFCE00AA2803 /* Font.h */, FA0B7B8A1A95902C000E1D17 /* Graphics.cpp */, FA0B7B8B1A95902C000E1D17 /* Graphics.h */, + FA84DE6427791C36002674C6 /* GraphicsReadback.cpp */, + FA84DE6527791C36002674C6 /* GraphicsReadback.h */, FADF54231E3DA5BA00012CC0 /* Mesh.cpp */, FADF54241E3DA5BA00012CC0 /* Mesh.h */, FA18CECC23DBC6E000263725 /* metal */, @@ -2857,6 +2877,8 @@ FADF54391E3DAFF700012CC0 /* wrap_Graphics.cpp */, FADF543A1E3DAFF700012CC0 /* wrap_Graphics.h */, FADF54371E3DAFBA00012CC0 /* wrap_Graphics.lua */, + FA84DE6F27795E22002674C6 /* wrap_GraphicsReadback.cpp */, + FA84DE6E27795E22002674C6 /* wrap_GraphicsReadback.h */, FADF54281E3DAADA00012CC0 /* wrap_Mesh.cpp */, FADF54291E3DAADA00012CC0 /* wrap_Mesh.h */, FADF541E1E3DA52C00012CC0 /* wrap_ParticleSystem.cpp */, @@ -2887,6 +2909,8 @@ FA28EBD41E352DB5003446F4 /* FenceSync.h */, FA0B7B911A95902C000E1D17 /* Graphics.cpp */, FA0B7B921A95902C000E1D17 /* Graphics.h */, + FA84DE6A277943F6002674C6 /* GraphicsReadback.cpp */, + FA84DE69277943F6002674C6 /* GraphicsReadback.h */, FA0B7B971A95902C000E1D17 /* OpenGL.cpp */, FA0B7B981A95902C000E1D17 /* OpenGL.h */, FA0B7B9D1A95902C000E1D17 /* Shader.cpp */, @@ -3354,6 +3378,8 @@ FA18CEE823DBC8D400263725 /* Buffer.mm */, FA18CED523DBC6E000263725 /* Graphics.h */, FA18CED323DBC6E000263725 /* Graphics.mm */, + FA6BDF8B280B62B600240F2A /* GraphicsReadback.h */, + FA6BDF88280B62A000240F2A /* GraphicsReadback.mm */, FA18CED423DBC6E000263725 /* Metal.h */, FA18CED023DBC6E000263725 /* Metal.mm */, FA18CECD23DBC6E000263725 /* Shader.h */, @@ -4338,6 +4364,7 @@ FABDA9C92552448300B5C523 /* b2_distance.h in Headers */, FA24348921D401CB00B8918A /* pch.h in Headers */, FAF140991E20934C00F898D2 /* PpTokens.h in Headers */, + FA84DE6827791C36002674C6 /* GraphicsReadback.h in Headers */, FAF1405B1E20934C00F898D2 /* InfoSink.h in Headers */, FA18CF2423DCF67900263725 /* spirv_cpp.hpp in Headers */, FA0B7B321A958EA3000E1D17 /* wuff.h in Headers */, @@ -4394,6 +4421,7 @@ FAC7CD961FE755B4006A60C7 /* lz4opt.h in Headers */, FA9D8DD31DEB56C3002CD881 /* pixelformat.h in Headers */, FABDA9DE2552448300B5C523 /* b2_prismatic_joint.h in Headers */, + FA84DE6B277943F6002674C6 /* GraphicsReadback.h in Headers */, FAF140A61E20934C00F898D2 /* ScanContext.h in Headers */, FABDA97B2552448200B5C523 /* b2_chain_polygon_contact.h in Headers */, FA0B7E4A1A95902C000E1D17 /* wrap_DistanceJoint.h in Headers */, @@ -4548,6 +4576,7 @@ FA18CF4723DD1A8100263725 /* ShaderStage.mm in Sources */, FA0B7ECC1A95902C000E1D17 /* wrap_Channel.cpp in Sources */, FA0B7E6D1A95902C000E1D17 /* wrap_RevoluteJoint.cpp in Sources */, + FA84DE7227795E22002674C6 /* wrap_GraphicsReadback.cpp in Sources */, FACA02FA1F5E397B0084B28F /* DataModule.cpp in Sources */, FA0B7E641A95902C000E1D17 /* wrap_PolygonShape.cpp in Sources */, FA4F2C031DE936C200CA37D7 /* auxiliar.c in Sources */, @@ -4619,6 +4648,7 @@ FADF53FE1E3D74F200012CC0 /* Text.cpp in Sources */, FA0B7D191A95902C000E1D17 /* TrueTypeRasterizer.cpp in Sources */, FAC271E723B5B5B400C200D3 /* renderstate.cpp in Sources */, + FA84DE6727791C36002674C6 /* GraphicsReadback.cpp in Sources */, FA0B7CFB1A95902C000E1D17 /* Filesystem.cpp in Sources */, FABDA9F82552448300B5C523 /* b2_dynamic_tree.cpp in Sources */, FABDA98B2552448300B5C523 /* b2_revolute_joint.cpp in Sources */, @@ -4651,6 +4681,7 @@ FABDA9A32552448300B5C523 /* b2_friction_joint.cpp in Sources */, FAF140AD1E20934C00F898D2 /* Versions.cpp in Sources */, FA0B793C1A958E3B000E1D17 /* runtime.cpp in Sources */, + FA6BDF8A280B62A000240F2A /* GraphicsReadback.mm in Sources */, FAF140DC1E20934C00F898D2 /* InitializeDll.cpp in Sources */, FA0B7DBC1A95902C000E1D17 /* Joystick.cpp in Sources */, FA0B7DAF1A95902C000E1D17 /* wrap_CompressedImageData.cpp in Sources */, @@ -4766,6 +4797,7 @@ FA0B7CF81A95902C000E1D17 /* FileData.cpp in Sources */, FA0B7DA61A95902C000E1D17 /* PNGHandler.cpp in Sources */, FAF6C9F523C2DE2900D7B5BC /* Logger.cpp in Sources */, + FA84DE6D277943F6002674C6 /* GraphicsReadback.cpp in Sources */, FAE64A932071365100BC7981 /* physfs_platform_haiku.cpp in Sources */, FA0B7E981A95902C000E1D17 /* Sound.cpp in Sources */, FA0B7E371A95902C000E1D17 /* WheelJoint.cpp in Sources */, @@ -4969,6 +5001,7 @@ FAF140A71E20934C00F898D2 /* ShaderLang.cpp in Sources */, FAC271E623B5B5B400C200D3 /* renderstate.cpp in Sources */, FA1BA09D1E16CFCE00AA2803 /* Font.cpp in Sources */, + FA84DE7127795E22002674C6 /* wrap_GraphicsReadback.cpp in Sources */, FA0B7E6C1A95902C000E1D17 /* wrap_RevoluteJoint.cpp in Sources */, FA0B7E631A95902C000E1D17 /* wrap_PolygonShape.cpp in Sources */, FAC7CD7B1FE35E95006A60C7 /* physfs_platform_unix.c in Sources */, @@ -5039,6 +5072,7 @@ FADF54341E3DAE6E00012CC0 /* wrap_SpriteBatch.cpp in Sources */, FA0B7E0C1A95902C000E1D17 /* Fixture.cpp in Sources */, FA0B7D181A95902C000E1D17 /* TrueTypeRasterizer.cpp in Sources */, + FA84DE6627791C36002674C6 /* GraphicsReadback.cpp in Sources */, FA0B7CFA1A95902C000E1D17 /* Filesystem.cpp in Sources */, FABDA9F72552448300B5C523 /* b2_dynamic_tree.cpp in Sources */, FABDA98A2552448300B5C523 /* b2_revolute_joint.cpp in Sources */, @@ -5071,6 +5105,7 @@ FABDA9A22552448300B5C523 /* b2_friction_joint.cpp in Sources */, 217DFC0D1D9F6D490055D849 /* unixudp.c in Sources */, FA0B7CDC1A95902C000E1D17 /* Source.cpp in Sources */, + FA6BDF89280B62A000240F2A /* GraphicsReadback.mm in Sources */, FA0B7DC41A95902C000E1D17 /* wrap_JoystickModule.cpp in Sources */, FA0B7E6F1A95902C000E1D17 /* wrap_RopeJoint.cpp in Sources */, FA24348721D401CB00B8918A /* attribute.cpp in Sources */, @@ -5190,6 +5225,7 @@ FA0B7DA51A95902C000E1D17 /* PNGHandler.cpp in Sources */, FA0B7B371A958EA3000E1D17 /* wuff_internal.c in Sources */, FA0B7E971A95902C000E1D17 /* Sound.cpp in Sources */, + FA84DE6C277943F6002674C6 /* GraphicsReadback.cpp in Sources */, FA4F2B791DE0125B00CA37D7 /* xxhash.c in Sources */, FA0B7E361A95902C000E1D17 /* WheelJoint.cpp in Sources */, FADF542F1E3DABF600012CC0 /* SpriteBatch.cpp in Sources */, diff --git a/src/modules/graphics/Graphics.cpp b/src/modules/graphics/Graphics.cpp index aa2410d61..7cc040940 100644 --- a/src/modules/graphics/Graphics.cpp +++ b/src/modules/graphics/Graphics.cpp @@ -235,6 +235,7 @@ Graphics::~Graphics() for (int i = 0; i < (int) SHADERSTAGE_MAX_ENUM; i++) cachedShaderStages[i].clear(); + pendingReadbacks.clear(); clearTemporaryResources(); Shader::deinitialize(); @@ -434,6 +435,46 @@ love::graphics::Text *Graphics::newText(graphics::Font *font, const std::vector< return new Text(font, text); } +love::data::ByteData *Graphics::readbackBuffer(Buffer *buffer, size_t offset, size_t size, data::ByteData *dest, size_t destoffset) +{ + StrongRef readback; + readback.set(newReadbackInternal(READBACK_IMMEDIATE, buffer, offset, size, dest, destoffset), Acquire::NORETAIN); + + auto data = readback->getBufferData(); + if (data == nullptr) + throw love::Exception("love.graphics.readbackBuffer failed."); + + data->retain(); + return data; +} + +GraphicsReadback *Graphics::readbackBufferAsync(Buffer *buffer, size_t offset, size_t size, data::ByteData *dest, size_t destoffset) +{ + auto readback = newReadbackInternal(READBACK_ASYNC, buffer, offset, size, dest, destoffset); + pendingReadbacks.push_back(readback); + return readback; +} + +image::ImageData *Graphics::readbackTexture(Texture *texture, int slice, int mipmap, const Rect &rect, image::ImageData *dest, int destx, int desty) +{ + StrongRef readback; + readback.set(newReadbackInternal(READBACK_IMMEDIATE, texture, slice, mipmap, rect, dest, destx, desty), Acquire::NORETAIN); + + auto imagedata = readback->getImageData(); + if (imagedata == nullptr) + throw love::Exception("love.graphics.readbackTexture failed."); + + imagedata->retain(); + return imagedata; +} + +GraphicsReadback *Graphics::readbackTextureAsync(Texture *texture, int slice, int mipmap, const Rect &rect, image::ImageData *dest, int destx, int desty) +{ + auto readback = newReadbackInternal(READBACK_ASYNC, texture, slice, mipmap, rect, dest, destx, desty); + pendingReadbacks.push_back(readback); + return readback; +} + void Graphics::cleanupCachedShaderStage(ShaderStageType type, const std::string &hashkey) { cachedShaderStages[type].erase(hashkey); @@ -1120,6 +1161,20 @@ void Graphics::clearTemporaryResources() temporaryBuffers.clear(); temporaryTextures.clear(); } + +void Graphics::updatePendingReadbacks() +{ + for (int i = (int)pendingReadbacks.size() - 1; i >= 0; i--) + { + pendingReadbacks[i]->update(); + if (pendingReadbacks[i]->isComplete()) + { + pendingReadbacks[i] = pendingReadbacks.back(); + pendingReadbacks.pop_back(); + } + } +} + void Graphics::intersectScissor(const Rect &rect) { Rect currect = states.back().scissorRect; diff --git a/src/modules/graphics/Graphics.h b/src/modules/graphics/Graphics.h index 161b35d7e..93cbdff2b 100644 --- a/src/modules/graphics/Graphics.h +++ b/src/modules/graphics/Graphics.h @@ -37,6 +37,7 @@ #include "Shader.h" #include "Quad.h" #include "Mesh.h" +#include "GraphicsReadback.h" #include "Deprecations.h" #include "renderstate.h" #include "math/Transform.h" @@ -460,6 +461,12 @@ public: Text *newText(Font *font, const std::vector &text = {}); + data::ByteData *readbackBuffer(Buffer *buffer, size_t offset, size_t size, data::ByteData *dest, size_t destoffset); + GraphicsReadback *readbackBufferAsync(Buffer *buffer, size_t offset, size_t size, data::ByteData *dest, size_t destoffset); + + image::ImageData *readbackTexture(Texture *texture, int slice, int mipmap, const Rect &rect, image::ImageData *dest, int destx, int desty); + GraphicsReadback *readbackTextureAsync(Texture *texture, int slice, int mipmap, const Rect &rect, image::ImageData *dest, int destx, int desty); + bool validateShader(bool gles, const std::vector &stages, const Shader::CompileOptions &options, std::string &err); /** @@ -989,6 +996,9 @@ protected: virtual Shader *newShaderInternal(StrongRef stages[SHADERSTAGE_MAX_ENUM]) = 0; virtual StreamBuffer *newStreamBuffer(BufferUsage type, size_t size) = 0; + virtual GraphicsReadback *newReadbackInternal(ReadbackMethod method, Buffer *buffer, size_t offset, size_t size, data::ByteData *dest, size_t destoffset) = 0; + virtual GraphicsReadback *newReadbackInternal(ReadbackMethod method, Texture *texture, int slice, int mipmap, const Rect &rect, image::ImageData *dest, int destx, int desty) = 0; + virtual bool dispatch(int x, int y, int z) = 0; virtual void setRenderTargetsInternal(const RenderTargets &rts, int pixelw, int pixelh, bool hasSRGBtexture) = 0; @@ -1002,6 +1012,8 @@ protected: void updateTemporaryResources(); void clearTemporaryResources(); + void updatePendingReadbacks(); + void restoreState(const DisplayState &s); void restoreStateChecked(const DisplayState &s); @@ -1023,6 +1035,7 @@ protected: StrongRef defaultFont; std::vector pendingScreenshotCallbacks; + std::vector> pendingReadbacks; BatchedDrawState batchedDrawState; diff --git a/src/modules/graphics/GraphicsReadback.cpp b/src/modules/graphics/GraphicsReadback.cpp new file mode 100644 index 000000000..30879a1a7 --- /dev/null +++ b/src/modules/graphics/GraphicsReadback.cpp @@ -0,0 +1,229 @@ +/** + * 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" +#include "image/ImageData.h" +#include "image/Image.h" + +namespace love +{ +namespace graphics +{ + +love::Type GraphicsReadback::type("GraphicsReadback", &Object::type); + +GraphicsReadback::GraphicsReadback(Graphics *gfx, ReadbackMethod method, Buffer *buffer, size_t offset, size_t size, love::data::ByteData *dest, size_t destoffset) + : dataType(DATA_BUFFER) + , method(method) + , bufferData(dest) +{ + const auto &caps = gfx->getCapabilities(); + + if (!caps.features[Graphics::FEATURE_COPY_BUFFER]) + throw love::Exception("readbackBuffer is not supported on this system (buffer copy support is required)."); + + if (offset + size > buffer->getSize()) + throw love::Exception("Invalid offset or size for the given Buffer."); + + if (dest != nullptr && destoffset + size > dest->getSize()) + throw love::Exception("Invalid destination offset or size for the given ByteData."); + + bufferDataOffset = dest != nullptr ? destoffset : 0; +} + +GraphicsReadback::GraphicsReadback(Graphics *gfx, ReadbackMethod method, Texture *texture, int slice, int mipmap, const Rect &rect, love::image::ImageData *dest, int destx, int desty) + : dataType(DATA_TEXTURE) + , method(method) + , imageData(dest) + , rect(rect) +{ + const auto &caps = gfx->getCapabilities(); + + if (gfx->isRenderTargetActive(texture)) + throw love::Exception("readbackTexture cannot be called while that Texture is an active render target."); + + if (!texture->isReadable()) + throw love::Exception("readbackTexture requires a readable Texture."); + + int tw = texture->getPixelWidth(mipmap); + int th = texture->getPixelHeight(mipmap); + auto texType = texture->getTextureType(); + + if (rect.x < 0 || rect.y < 0 || rect.w <= 0 || rect.h <= 0 || (rect.x + rect.w) > tw || (rect.y + rect.h) > th) + throw love::Exception("Invalid rectangle dimensions."); + + if (slice < 0 || (texType == TEXTURE_VOLUME && slice >= texture->getDepth(mipmap)) + || (texType == TEXTURE_2D_ARRAY && slice >= texture->getLayerCount()) + || (texType == TEXTURE_CUBE && slice >= 6)) + { + throw love::Exception("Invalid slice index."); + } + + textureFormat = getLinearPixelFormat(texture->getPixelFormat()); + + if (!image::ImageData::validPixelFormat(textureFormat)) + { + const char *formatname = "unknown"; + love::getConstant(textureFormat, formatname); + throw love::Exception("ImageData with the '%s' pixel format is not supported.", formatname); + } + + bool isRT = texture->isRenderTarget(); + + if (method == READBACK_ASYNC) + { + if (isRT && !caps.features[Graphics::FEATURE_COPY_RENDER_TARGET_TO_BUFFER]) + throw love::Exception("readbackTextureAsync is not supported on this system."); + else if (!isRT && !caps.features[Graphics::FEATURE_COPY_TEXTURE_TO_BUFFER]) + throw love::Exception("readbackTextureAsync a with non-render-target textures is not supported on this system."); + } + else + { + if (!isRT && !caps.features[Graphics::FEATURE_COPY_TEXTURE_TO_BUFFER]) + throw love::Exception("readbackTexture with a non-render-target texture is not supported on this system."); + } + + if (dest != nullptr) + { + if (dest->getFormat() != textureFormat) + throw love::Exception("Destination ImageData pixel format must match the source Texture's format."); + + if (destx < 0 || desty < 0) + throw love::Exception("Invalid destination ImageData x/y coordinates."); + + if (destx + rect.w > dest->getWidth() || desty + rect.h > dest->getHeight()) + throw love::Exception("The specified rectangle does not fit within the destination ImageData's dimensions."); + } + + imageDataX = dest != nullptr ? destx : 0; + imageDataY = dest != nullptr ? desty : 0; +} + +GraphicsReadback::~GraphicsReadback() +{ +} + +love::data::ByteData *GraphicsReadback::getBufferData() const +{ + if (!isComplete()) + return nullptr; + return bufferData; +} + +love::image::ImageData *GraphicsReadback::getImageData() const +{ + if (!isComplete()) + return nullptr; + return imageData; +} + +void *GraphicsReadback::prepareReadbackDest(size_t size) +{ + if (dataType == DATA_TEXTURE) + { + if (imageData.get()) + { + // Not the cleanest, but should work since uncompressed formats always + // have 1x1 blocks. + int pixels = imageDataY * imageData->getWidth() + imageDataX; + size_t offset = getPixelFormatUncompressedRowSize(textureFormat, pixels); + + return (uint8 *) imageData->getData() + offset; + } + else + { + auto module = Module::getInstance(Module::M_IMAGE); + if (module == nullptr) + throw love::Exception("The love.image module must be loaded for readbackTexture."); + + imageData.set(module->newImageData(rect.w, rect.h, textureFormat, nullptr), Acquire::NORETAIN); + return imageData->getData(); + } + } + else + { + if (!bufferData.get()) + bufferData.set(new love::data::ByteData(size, false), Acquire::NORETAIN); + + return (uint8 *) bufferData->getData() + bufferDataOffset; + } +} + +GraphicsReadback::Status GraphicsReadback::readbackBuffer(Buffer *buffer, size_t offset, size_t size) +{ + if (buffer == nullptr) + return STATUS_ERROR; + + const void *data = buffer->map(Buffer::MAP_READ_ONLY, offset, size); + + if (data == nullptr) + return STATUS_ERROR; + + bool success = true; + + try + { + void *dest = prepareReadbackDest(size); + if (dest == nullptr) + return STATUS_ERROR; + + if (imageData.get()) + { + love::thread::Lock lock(imageData->getMutex()); + + if (imageData->getWidth() != rect.w) + { + // Readback of compressed textures into ImageData isn't supported, + // so this is fine. + size_t stride = getPixelFormatUncompressedRowSize(textureFormat, imageData->getWidth()); + size_t rowsize = getPixelFormatUncompressedRowSize(textureFormat, rect.w); + + for (int i = 0; i < rect.h; i++) + { + memcpy(dest, data, rowsize); + dest = (uint8 *) dest + stride; + data = (uint8 *) data + rowsize; + } + } + else + { + memcpy(dest, data, std::min(size, imageData->getSize())); + } + } + else + { + memcpy(dest, data, std::min(size, bufferData->getSize())); + } + } + catch (love::Exception &) + { + success = false; + } + + buffer->unmap(offset, size); + return success ? STATUS_COMPLETE : STATUS_ERROR; +} + +} // graphics +} // love diff --git a/src/modules/graphics/GraphicsReadback.h b/src/modules/graphics/GraphicsReadback.h new file mode 100644 index 000000000..264985393 --- /dev/null +++ b/src/modules/graphics/GraphicsReadback.h @@ -0,0 +1,112 @@ +/** + * 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 "common/config.h" +#include "common/int.h" +#include "common/math.h" +#include "common/Object.h" +#include "common/StringMap.h" +#include "common/pixelformat.h" + +namespace love::image +{ +class ImageData; +class CompressedImageData; +} + +namespace love::data +{ +class ByteData; +} + +namespace love +{ +namespace graphics +{ + +class Buffer; +class Texture; +class Graphics; + +enum ReadbackMethod +{ + READBACK_IMMEDIATE, + READBACK_ASYNC, +}; + +class GraphicsReadback : public love::Object +{ +public: + + enum Status + { + STATUS_WAITING, + STATUS_COMPLETE, + STATUS_ERROR, + STATUS_MAX_ENUM + }; + + static love::Type type; + + GraphicsReadback(Graphics *gfx, ReadbackMethod method, Buffer *buffer, size_t offset, size_t size, love::data::ByteData *dest, size_t destoffset); + GraphicsReadback(Graphics *gfx, ReadbackMethod method, Texture *texture, int slice, int mipmap, const Rect &rect, love::image::ImageData *dest, int destx, int desty); + virtual ~GraphicsReadback(); + + virtual void wait() = 0; + virtual void update() = 0; + + bool isComplete() const { return status != STATUS_WAITING; } + ReadbackMethod getMethod() const { return method; } + bool hasError() const { return status == STATUS_ERROR; } + + love::data::ByteData *getBufferData() const; + love::image::ImageData *getImageData() const; + +protected: + + enum DataType + { + DATA_BUFFER, + DATA_TEXTURE, + }; + + void *prepareReadbackDest(size_t size); + Status readbackBuffer(Buffer *buffer, size_t offset, size_t size); + + DataType dataType; + ReadbackMethod method; + Status status = STATUS_WAITING; + + StrongRef bufferData; + size_t bufferDataOffset = 0; + + StrongRef imageData; + Rect rect = {}; + PixelFormat textureFormat = PIXELFORMAT_UNKNOWN; + int imageDataX = 0; + int imageDataY = 0; + +}; // GraphicsReadback + +} // graphics +} // love diff --git a/src/modules/graphics/Texture.cpp b/src/modules/graphics/Texture.cpp index 7da786e4d..8b0ceb069 100644 --- a/src/modules/graphics/Texture.cpp +++ b/src/modules/graphics/Texture.cpp @@ -563,47 +563,6 @@ void Texture::generateMipmaps() generateMipmapsInternal(); } -love::image::ImageData *Texture::newImageData(love::image::Image *module, int slice, int mipmap, const Rect &r) -{ - if (!isReadable()) - throw love::Exception("Texture:newImageData cannot be called on non-readable Textures."); - - if (!isRenderTarget()) - throw love::Exception("Texture:newImageData can only be called on render target Textures."); - - if (isPixelFormatDepthStencil(getPixelFormat())) - throw love::Exception("Texture:newImageData cannot be called on Textures with depth/stencil pixel formats."); - - if (r.x < 0 || r.y < 0 || r.w <= 0 || r.h <= 0 || (r.x + r.w) > getPixelWidth(mipmap) || (r.y + r.h) > getPixelHeight(mipmap)) - throw love::Exception("Invalid rectangle dimensions."); - - if (slice < 0 || (texType == TEXTURE_VOLUME && slice >= getDepth(mipmap)) - || (texType == TEXTURE_2D_ARRAY && slice >= layers) - || (texType == TEXTURE_CUBE && slice >= 6)) - { - throw love::Exception("Invalid slice index."); - } - - Graphics *gfx = Module::getInstance(Module::M_GRAPHICS); - if (gfx != nullptr && gfx->isRenderTargetActive(this)) - throw love::Exception("Texture:newImageData cannot be called while that Texture is an active render target."); - - PixelFormat dataformat = getLinearPixelFormat(getPixelFormat()); - - if (!image::ImageData::validPixelFormat(dataformat)) - { - const char *formatname = "unknown"; - love::getConstant(dataformat, formatname); - throw love::Exception("ImageData with the '%s' pixel format is not supported.", formatname); - } - - auto imagedata = module->newImageData(r.w, r.h, dataformat); - - readbackImageData(imagedata, slice, mipmap, r); - - return imagedata; -} - TextureType Texture::getTextureType() const { return texType; diff --git a/src/modules/graphics/Texture.h b/src/modules/graphics/Texture.h index 415e4ace7..17402ac57 100644 --- a/src/modules/graphics/Texture.h +++ b/src/modules/graphics/Texture.h @@ -245,8 +245,6 @@ public: void generateMipmaps(); - 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; @@ -313,7 +311,6 @@ protected: bool supportsGenerateMipmaps(const char *&outReason) const; virtual void generateMipmapsInternal() = 0; - virtual void readbackImageData(love::image::ImageData *imagedata, int slice, int mipmap, const Rect &rect) = 0; bool validateDimensions(bool throwException) const; diff --git a/src/modules/graphics/metal/Graphics.h b/src/modules/graphics/metal/Graphics.h index 9274a0d6e..7ff023eb7 100644 --- a/src/modules/graphics/metal/Graphics.h +++ b/src/modules/graphics/metal/Graphics.h @@ -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 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; diff --git a/src/modules/graphics/metal/Graphics.mm b/src/modules/graphics/metal/Graphics.mm index 90bc1edfd..a12b2cc5f 100644 --- a/src/modules/graphics/metal/Graphics.mm +++ b/src/modules/graphics/metal/Graphics.mm @@ -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(); }} diff --git a/src/modules/graphics/metal/GraphicsReadback.h b/src/modules/graphics/metal/GraphicsReadback.h new file mode 100644 index 000000000..851420b8f --- /dev/null +++ b/src/modules/graphics/metal/GraphicsReadback.h @@ -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 + +#import + +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 cmd; + std::atomic_bool done; + + StrongRef stagingBuffer; + +}; // GraphicsReadback + +} // love::graphics::metal diff --git a/src/modules/graphics/metal/GraphicsReadback.mm b/src/modules/graphics/metal/GraphicsReadback.mm new file mode 100644 index 000000000..128a9f7ca --- /dev/null +++ b/src/modules/graphics/metal/GraphicsReadback.mm @@ -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 _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 _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(Module::M_GRAPHICS); + if (gfx != nullptr) + gfx->releaseTemporaryBuffer(stagingBuffer); + stagingBuffer.set(nullptr); + } + } +} + +} // love::graphics::metal diff --git a/src/modules/graphics/metal/Texture.h b/src/modules/graphics/metal/Texture.h index 5084ebc25..0f870c9df 100644 --- a/src/modules/graphics/metal/Texture.h +++ b/src/modules/graphics/metal/Texture.h @@ -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 texture; id msaaTexture; diff --git a/src/modules/graphics/metal/Texture.mm b/src/modules/graphics/metal/Texture.mm index 0c2adf944..d2602640d 100644 --- a/src/modules/graphics/metal/Texture.mm +++ b/src/modules/graphics/metal/Texture.mm @@ -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 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 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 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 encoder = Graphics::getInstance()->useBlitEncoder(); diff --git a/src/modules/graphics/opengl/FenceSync.cpp b/src/modules/graphics/opengl/FenceSync.cpp index d1f5fa4c6..a87d7501a 100644 --- a/src/modules/graphics/opengl/FenceSync.cpp +++ b/src/modules/graphics/opengl/FenceSync.cpp @@ -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) diff --git a/src/modules/graphics/opengl/FenceSync.h b/src/modules/graphics/opengl/FenceSync.h index c7b14e44f..0b72a61db 100644 --- a/src/modules/graphics/opengl/FenceSync.h +++ b/src/modules/graphics/opengl/FenceSync.h @@ -42,6 +42,7 @@ public: ~FenceSync(); bool fence(); + bool isComplete() const; bool cpuWait(); void cleanup(); diff --git a/src/modules/graphics/opengl/Graphics.cpp b/src/modules/graphics/opengl/Graphics.cpp index 92e48634e..83732f8a1 100644 --- a/src/modules/graphics/opengl/Graphics.cpp +++ b/src/modules/graphics/opengl/Graphics.cpp @@ -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(); } diff --git a/src/modules/graphics/opengl/Graphics.h b/src/modules/graphics/opengl/Graphics.h index f9bcb6523..8f062e055 100644 --- a/src/modules/graphics/opengl/Graphics.h +++ b/src/modules/graphics/opengl/Graphics.h @@ -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 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; diff --git a/src/modules/graphics/opengl/GraphicsReadback.cpp b/src/modules/graphics/opengl/GraphicsReadback.cpp new file mode 100644 index 000000000..9eb0c25bd --- /dev/null +++ b/src/modules/graphics/opengl/GraphicsReadback.cpp @@ -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(Module::M_GRAPHICS); + if (gfx != nullptr) + gfx->releaseTemporaryBuffer(stagingBuffer); + stagingBuffer.set(nullptr); + } + } +} + +} // opengl +} // graphics +} // love diff --git a/src/modules/graphics/opengl/GraphicsReadback.h b/src/modules/graphics/opengl/GraphicsReadback.h new file mode 100644 index 000000000..35553f7df --- /dev/null +++ b/src/modules/graphics/opengl/GraphicsReadback.h @@ -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 stagingBuffer; + +}; // GraphicsReadback + +} // opengl +} // graphics +} // love diff --git a/src/modules/graphics/opengl/Texture.cpp b/src/modules/graphics/opengl/Texture.cpp index acdef9e72..499ef7140 100644 --- a/src/modules/graphics/opengl/Texture.cpp +++ b/src/modules/graphics/opengl/Texture.cpp @@ -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); } diff --git a/src/modules/graphics/opengl/Texture.h b/src/modules/graphics/opengl/Texture.h index 8a5163c4b..483af447d 100644 --- a/src/modules/graphics/opengl/Texture.h +++ b/src/modules/graphics/opengl/Texture.h @@ -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; diff --git a/src/modules/graphics/wrap_Graphics.cpp b/src/modules/graphics/wrap_Graphics.cpp index d919c4130..83a649440 100644 --- a/src/modules/graphics/wrap_Graphics.cpp +++ b/src/modules/graphics/wrap_Graphics.cpp @@ -2141,6 +2141,126 @@ int w_newVideo(lua_State *L) return 1; } +int w_readbackBuffer(lua_State *L) +{ + Buffer *b = luax_checkbuffer(L, 1); + lua_Integer offset = luaL_optinteger(L, 2, 0); + lua_Integer size = luaL_optinteger(L, 3, b->getSize() - offset); + + data::ByteData *dest = nullptr; + size_t destoffset = 0; + if (!lua_isnoneornil(L, 4)) + { + dest = luax_checktype(L, 4); + destoffset = (size_t) luaL_optinteger(L, 5, 0); + } + + love::data::ByteData *data = nullptr; + luax_catchexcept(L, [&]() { data = instance()->readbackBuffer(b, offset, size, dest, destoffset); }); + + luax_pushtype(L, data); + data->release(); + return 1; +} + +int w_readbackBufferAsync(lua_State *L) +{ + Buffer *b = luax_checkbuffer(L, 1); + lua_Integer offset = luaL_optinteger(L, 2, 0); + lua_Integer size = luaL_optinteger(L, 3, b->getSize() - offset); + + data::ByteData *dest = nullptr; + size_t destoffset = 0; + if (!lua_isnoneornil(L, 4)) + { + dest = luax_checktype(L, 4); + destoffset = (size_t) luaL_optinteger(L, 5, 0); + } + + GraphicsReadback *r = nullptr; + luax_catchexcept(L, [&]() { r = instance()->readbackBufferAsync(b, offset, size, dest, destoffset); }); + + luax_pushtype(L, r); + r->release(); + return 1; +} + +int w_readbackTexture(lua_State *L) +{ + Texture *t = luax_checktexture(L, 1); + + int slice = 0; + if (t->getTextureType() != TEXTURE_2D) + slice = (int) luaL_checkinteger(L, 2) - 1; + + int 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); + rect.y = (int) luaL_checkinteger(L, 5); + rect.w = (int) luaL_checkinteger(L, 6); + rect.h = (int) luaL_checkinteger(L, 7); + } + + image::ImageData *dest = nullptr; + int destx = 0; + int desty = 0; + + if (!lua_isnoneornil(L, 8)) + { + dest = luax_checktype(L, 8); + destx = (int) luaL_optinteger(L, 9, 0); + desty = (int) luaL_optinteger(L, 10, 0); + } + + image::ImageData *imagedata = nullptr; + luax_catchexcept(L, [&]() { imagedata = instance()->readbackTexture(t, slice, mipmap, rect, dest, destx, desty); }); + + luax_pushtype(L, imagedata); + imagedata->release(); + return 1; +} + +int w_readbackTextureAsync(lua_State *L) +{ + Texture *t = luax_checktexture(L, 1); + + int slice = 0; + if (t->getTextureType() != TEXTURE_2D) + slice = (int) luaL_checkinteger(L, 2) - 1; + + int 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); + rect.y = (int) luaL_checkinteger(L, 5); + rect.w = (int) luaL_checkinteger(L, 6); + rect.h = (int) luaL_checkinteger(L, 7); + } + + image::ImageData *dest = nullptr; + int destx = 0; + int desty = 0; + + if (!lua_isnoneornil(L, 8)) + { + dest = luax_checktype(L, 8); + destx = (int) luaL_optinteger(L, 9, 0); + desty = (int) luaL_optinteger(L, 10, 0); + } + + GraphicsReadback *r = nullptr; + luax_catchexcept(L, [&]() { r = instance()->readbackTextureAsync(t, slice, mipmap, rect, dest, destx, desty); }); + + luax_pushtype(L, r); + r->release(); + return 1; +} + int w_setColor(lua_State *L) { Colorf c; @@ -3650,6 +3770,11 @@ static const luaL_Reg functions[] = { "newText", w_newText }, { "_newVideo", w_newVideo }, + { "readbackBuffer", w_readbackBuffer }, + { "readbackBufferAsync", w_readbackBufferAsync }, + { "readbackTexture", w_readbackTexture }, + { "readbackTextureAsync", w_readbackTextureAsync }, + { "validateShader", w_validateShader }, { "setCanvas", w_setCanvas }, @@ -3787,6 +3912,7 @@ static const lua_CFunction types[] = luaopen_font, luaopen_quad, luaopen_graphicsbuffer, + luaopen_graphicsreadback, luaopen_spritebatch, luaopen_particlesystem, luaopen_shader, diff --git a/src/modules/graphics/wrap_Graphics.h b/src/modules/graphics/wrap_Graphics.h index 50ffe7fb8..a2b9d0b89 100644 --- a/src/modules/graphics/wrap_Graphics.h +++ b/src/modules/graphics/wrap_Graphics.h @@ -32,6 +32,7 @@ #include "wrap_Text.h" #include "wrap_Video.h" #include "wrap_Buffer.h" +#include "wrap_GraphicsReadback.h" #include "Graphics.h" namespace love diff --git a/src/modules/graphics/wrap_GraphicsReadback.cpp b/src/modules/graphics/wrap_GraphicsReadback.cpp new file mode 100644 index 000000000..1b6251bd5 --- /dev/null +++ b/src/modules/graphics/wrap_GraphicsReadback.cpp @@ -0,0 +1,95 @@ +/** + * Copyright (c) 2006-2021 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 "wrap_GraphicsReadback.h" +#include "data/ByteData.h" +#include "image/ImageData.h" + +namespace love +{ +namespace graphics +{ + +GraphicsReadback *luax_checkgraphicsreadback(lua_State *L, int idx) +{ + return luax_checktype(L, idx); +} + +int w_GraphicsReadback_isComplete(lua_State *L) +{ + GraphicsReadback *t = luax_checkgraphicsreadback(L, 1); + luax_pushboolean(L, t->isComplete()); + return 1; +} + +int w_GraphicsReadback_hasError(lua_State *L) +{ + GraphicsReadback *t = luax_checkgraphicsreadback(L, 1); + luax_pushboolean(L, t->hasError()); + return 1; +} + +int w_GraphicsReadback_wait(lua_State *L) +{ + GraphicsReadback *t = luax_checkgraphicsreadback(L, 1); + t->wait(); + return 0; +} + +int w_GraphicsReadback_update(lua_State *L) +{ + GraphicsReadback *t = luax_checkgraphicsreadback(L, 1); + luax_catchexcept(L, [&]() { t->update(); }); + return 0; +} + +int w_GraphicsReadback_getBufferData(lua_State *L) +{ + GraphicsReadback *t = luax_checkgraphicsreadback(L, 1); + luax_pushtype(L, t->getBufferData()); + return 1; +} + +int w_GraphicsReadback_getImageData(lua_State *L) +{ + GraphicsReadback *t = luax_checkgraphicsreadback(L, 1); + luax_pushtype(L, t->getImageData()); + return 1; +} + +static const luaL_Reg w_GraphicsReadback_functions[] = +{ + { "isComplete", w_GraphicsReadback_isComplete }, + { "hasError", w_GraphicsReadback_hasError }, + { "wait", w_GraphicsReadback_wait }, + { "update", w_GraphicsReadback_update }, + { "getBufferData", w_GraphicsReadback_getBufferData }, + { "getImageData", w_GraphicsReadback_getImageData }, + { 0, 0 } +}; + +extern "C" int luaopen_graphicsreadback(lua_State *L) +{ + return luax_register_type(L, &GraphicsReadback::type, w_GraphicsReadback_functions, nullptr); +} + +} // graphics +} // love diff --git a/src/modules/graphics/wrap_GraphicsReadback.h b/src/modules/graphics/wrap_GraphicsReadback.h new file mode 100644 index 000000000..a22139e51 --- /dev/null +++ b/src/modules/graphics/wrap_GraphicsReadback.h @@ -0,0 +1,36 @@ +/** + * Copyright (c) 2006-2021 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 "common/runtime.h" +#include "GraphicsReadback.h" + +namespace love +{ +namespace graphics +{ + +GraphicsReadback *luax_checkgraphicsreadback(lua_State *L, int idx); +extern "C" int luaopen_graphicsreadback(lua_State *L); + +} // graphics +} // love diff --git a/src/modules/graphics/wrap_Texture.cpp b/src/modules/graphics/wrap_Texture.cpp index 235d9bd84..4a416a097 100644 --- a/src/modules/graphics/wrap_Texture.cpp +++ b/src/modules/graphics/wrap_Texture.cpp @@ -385,16 +385,15 @@ int w_Texture_replacePixels(lua_State *L) int w_Texture_newImageData(lua_State *L) { + luax_markdeprecated(L, 1, "Texture:newImageData", API_METHOD, DEPRECATED_RENAMED, "love.graphics.readbackTexture"); + Texture *t = luax_checktexture(L, 1); - love::image::Image *image = luax_getmodule(L, love::image::Image::type); int slice = 0; - int mipmap = 0; - if (t->getTextureType() != TEXTURE_2D) slice = (int) luaL_checkinteger(L, 2) - 1; - mipmap = (int) luaL_optinteger(L, 3, 1) - 1; + int mipmap = (int) luaL_optinteger(L, 3, 1) - 1; Rect rect = {0, 0, t->getPixelWidth(mipmap), t->getPixelHeight(mipmap)}; if (!lua_isnoneornil(L, 4)) @@ -405,8 +404,12 @@ int w_Texture_newImageData(lua_State *L) rect.h = (int) luaL_checkinteger(L, 7); } + auto gfx = Module::getInstance(Module::M_GRAPHICS); + if (gfx == nullptr) + return luaL_error(L, "Cannot find Graphics module."); + love::image::ImageData *img = nullptr; - luax_catchexcept(L, [&](){ img = t->newImageData(image, slice, mipmap, rect); }); + luax_catchexcept(L, [&](){ img = gfx->readbackTexture(t, slice, mipmap, rect, nullptr, 0, 0); }); luax_pushtype(L, img); img->release(); @@ -501,8 +504,11 @@ const luaL_Reg w_Texture_functions[] = { "setDepthSampleMode", w_Texture_setDepthSampleMode }, { "generateMipmaps", w_Texture_generateMipmaps }, { "replacePixels", w_Texture_replacePixels }, - { "newImageData", w_Texture_newImageData }, { "renderTo", w_Texture_renderTo }, + + // Deprecated + { "newImageData", w_Texture_newImageData }, + { 0, 0 } };