From 65b898903c6c688d0f971d1235bfcd02a2f8f18e Mon Sep 17 00:00:00 2001 From: Miku AuahDark Date: Tue, 3 May 2022 19:29:24 +0800 Subject: [PATCH 01/18] Add love.audio.getOutputDevice(s). --- src/modules/audio/Audio.h | 10 +++++++++ src/modules/audio/null/Audio.cpp | 9 ++++++++ src/modules/audio/null/Audio.h | 3 +++ src/modules/audio/openal/Audio.cpp | 36 ++++++++++++++++++++++++++++++ src/modules/audio/openal/Audio.h | 3 +++ src/modules/audio/wrap_Audio.cpp | 27 ++++++++++++++++++++++ 6 files changed, 88 insertions(+) diff --git a/src/modules/audio/Audio.h b/src/modules/audio/Audio.h index 8a4ae8bcf..cd617514b 100644 --- a/src/modules/audio/Audio.h +++ b/src/modules/audio/Audio.h @@ -297,6 +297,16 @@ public: virtual void pauseContext() = 0; virtual void resumeContext() = 0; + /** + * Get current output device name. + */ + virtual std::string getOutputDevice() = 0; + + /** + * Retrieve list of available output devices. + */ + virtual void getOutputDevices(std::vector &list) = 0; + private: static StringMap::Entry distanceModelEntries[]; diff --git a/src/modules/audio/null/Audio.cpp b/src/modules/audio/null/Audio.cpp index 588f651ce..70babb0f7 100644 --- a/src/modules/audio/null/Audio.cpp +++ b/src/modules/audio/null/Audio.cpp @@ -211,6 +211,15 @@ void Audio::resumeContext() { } +std::string Audio::getOutputDevice() +{ + return ""; +} + +void Audio::getOutputDevices(std::vector &/*list*/) +{ +} + } // null } // audio diff --git a/src/modules/audio/null/Audio.h b/src/modules/audio/null/Audio.h index 062bb9999..11689d151 100644 --- a/src/modules/audio/null/Audio.h +++ b/src/modules/audio/null/Audio.h @@ -89,6 +89,9 @@ public: void pauseContext(); void resumeContext(); + std::string getOutputDevice(); + void getOutputDevices(std::vector &list); + private: float volume; DistanceModel distanceModel; diff --git a/src/modules/audio/openal/Audio.cpp b/src/modules/audio/openal/Audio.cpp index 68645e351..655c063a7 100644 --- a/src/modules/audio/openal/Audio.cpp +++ b/src/modules/audio/openal/Audio.cpp @@ -93,6 +93,18 @@ ALenum Audio::getFormat(int bitDepth, int channels) return AL_NONE; } +static const char *getDeviceSpecifier(ALCdevice *device) +{ +#ifndef ALC_ALL_DEVICES_SPECIFIER + constexpr ALCenum ALC_ALL_DEVICES_SPECIFIER = 0x1013; +#endif + static ALCenum deviceEnum = alcIsExtensionPresent(nullptr, "ALC_ENUMERATE_ALL_EXT") + ? ALC_ALL_DEVICES_SPECIFIER + : ALC_DEVICE_SPECIFIER; + + return alcGetString(device, deviceEnum); +} + Audio::Audio() : device(nullptr) , context(nullptr) @@ -314,6 +326,30 @@ void Audio::resumeContext() alcMakeContextCurrent(context); } +std::string Audio::getOutputDevice() +{ + const char *dev = getDeviceSpecifier(device); + + if (dev == nullptr) + throw Exception("Failed to get current device: %s", alcGetString(device, alcGetError(device))); + + return dev; +} + +void Audio::getOutputDevices(std::vector &list) +{ + const char *devices = getDeviceSpecifier(nullptr); + + if (devices == nullptr) + throw Exception("Failed to enumerate devices: %s", alcGetString(nullptr, alcGetError(nullptr))); + + for (const char *device = devices; *device; device++) + { + list.emplace_back(device); + device += list.back().length(); + } +} + void Audio::setVolume(float volume) { alListenerf(AL_GAIN, volume); diff --git a/src/modules/audio/openal/Audio.h b/src/modules/audio/openal/Audio.h index 02b8a5f51..58af10987 100644 --- a/src/modules/audio/openal/Audio.h +++ b/src/modules/audio/openal/Audio.h @@ -127,6 +127,9 @@ public: bool getEffectID(const char *name, ALuint &id); + std::string getOutputDevice(); + void getOutputDevices(std::vector &list); + private: void initializeEFX(); // The OpenAL device. diff --git a/src/modules/audio/wrap_Audio.cpp b/src/modules/audio/wrap_Audio.cpp index 21c8429e3..3ef9ab884 100644 --- a/src/modules/audio/wrap_Audio.cpp +++ b/src/modules/audio/wrap_Audio.cpp @@ -543,6 +543,31 @@ int w_setMixWithSystem(lua_State *L) return 1; } +int w_getOutputDevice(lua_State* L) +{ + std::string device; + + luax_catchexcept(L, [&]() { device = instance()->getOutputDevice(); }); + luax_pushstring(L, device); + return 1; +} + +int w_getOutputDevices(lua_State* L) +{ + std::vector list; + + luax_catchexcept(L, [&]() { instance()->getOutputDevices(list); }); + lua_createtable(L, 0, (int) list.size()); + for (int i = 0; i < (int) list.size(); i++) + { + lua_pushnumber(L, i + 1); + lua_pushstring(L, list[i].c_str()); + lua_rawset(L, -3); + } + + return 1; +} + // List of functions to wrap. static const luaL_Reg functions[] = { @@ -574,6 +599,8 @@ static const luaL_Reg functions[] = { "getMaxSourceEffects", w_getMaxSourceEffects }, { "isEffectsSupported", w_isEffectsSupported }, { "setMixWithSystem", w_setMixWithSystem }, + { "getOutputDevice", w_getOutputDevice }, + { "getOutputDevices", w_getOutputDevices }, { 0, 0 } }; From 5e0f0dc02ef0543eea09450b95368c888efa03db Mon Sep 17 00:00:00 2001 From: Miku AuahDark Date: Tue, 3 May 2022 20:16:26 +0800 Subject: [PATCH 02/18] Add love.audio.setOutputDevice --- src/modules/audio/Audio.cpp | 5 +++++ src/modules/audio/Audio.h | 5 +++++ src/modules/audio/openal/Audio.cpp | 35 +++++++++++++++++++++++++----- src/modules/audio/openal/Audio.h | 2 ++ src/modules/audio/wrap_Audio.cpp | 22 +++++++++++++++++++ 5 files changed, 64 insertions(+), 5 deletions(-) diff --git a/src/modules/audio/Audio.cpp b/src/modules/audio/Audio.cpp index 2de9ce871..645c95bb6 100644 --- a/src/modules/audio/Audio.cpp +++ b/src/modules/audio/Audio.cpp @@ -78,6 +78,11 @@ bool Audio::setMixWithSystem(bool mix) #endif } +void Audio::setOutputDevice(const char */*name*/) +{ + throw love::Exception("Re-setting output device is not supported."); +} + StringMap::Entry Audio::distanceModelEntries[] = { {"none", Audio::DISTANCE_NONE}, diff --git a/src/modules/audio/Audio.h b/src/modules/audio/Audio.h index cd617514b..474d8a939 100644 --- a/src/modules/audio/Audio.h +++ b/src/modules/audio/Audio.h @@ -307,6 +307,11 @@ public: */ virtual void getOutputDevices(std::vector &list) = 0; + /** + * Set the output device to specified device name. + */ + virtual void setOutputDevice(const char *name); + private: static StringMap::Entry distanceModelEntries[]; diff --git a/src/modules/audio/openal/Audio.cpp b/src/modules/audio/openal/Audio.cpp index 655c063a7..88c4a4aef 100644 --- a/src/modules/audio/openal/Audio.cpp +++ b/src/modules/audio/openal/Audio.cpp @@ -98,7 +98,7 @@ static const char *getDeviceSpecifier(ALCdevice *device) #ifndef ALC_ALL_DEVICES_SPECIFIER constexpr ALCenum ALC_ALL_DEVICES_SPECIFIER = 0x1013; #endif - static ALCenum deviceEnum = alcIsExtensionPresent(nullptr, "ALC_ENUMERATE_ALL_EXT") + static ALCenum deviceEnum = alcIsExtensionPresent(nullptr, "ALC_ENUMERATE_ALL_EXT") == ALC_TRUE ? ALC_ALL_DEVICES_SPECIFIER : ALC_DEVICE_SPECIFIER; @@ -112,6 +112,9 @@ Audio::Audio() , poolThread(nullptr) , distanceModel(DISTANCE_INVERSE_CLAMPED) { + attribs.push_back(0); + attribs.push_back(0); + // Before opening new device, check if recording // is requested. if (getRequestRecordingPermission()) @@ -134,12 +137,11 @@ Audio::Audio() throw love::Exception("Could not open device."); #ifdef ALC_EXT_EFX - ALint attribs[4] = { ALC_MAX_AUXILIARY_SENDS, MAX_SOURCE_EFFECTS, 0, 0 }; -#else - ALint *attribs = nullptr; + attribs.insert(attribs.begin(), ALC_MAX_AUXILIARY_SENDS); + attribs.insert(attribs.begin() + 1, MAX_SOURCE_EFFECTS); #endif - context = alcCreateContext(device, attribs); + context = alcCreateContext(device, attribs.data()); if (context == nullptr) throw love::Exception("Could not create context."); @@ -350,6 +352,29 @@ void Audio::getOutputDevices(std::vector &list) } } +void Audio::setOutputDevice(const char* name) +{ +#undef ALC_SOFT_reopen_device +#ifndef ALC_SOFT_reopen_device + typedef ALCboolean (ALC_APIENTRY*LPALCREOPENDEVICESOFT)(ALCdevice *device, + const ALCchar *deviceName, const ALCint *attribs); +#endif + static LPALCREOPENDEVICESOFT alcReopenDeviceSOFT = alcIsExtensionPresent(device, "ALC_SOFT_reopen_device") == ALC_TRUE + ? (LPALCREOPENDEVICESOFT) alcGetProcAddress(device, "alcReopenDeviceSOFT") + : nullptr; + + if (alcReopenDeviceSOFT == nullptr) + { + // Default implementation throws exception. To make + // error message consistent, call the base class. + love::audio::Audio::setOutputDevice(name); + return; + } + + if (alcReopenDeviceSOFT(device, (const ALCchar *) name, attribs.data()) == ALC_FALSE) + throw love::Exception("Cannot set output device: %s", alcGetString(device, alcGetError(device))); +} + void Audio::setVolume(float volume) { alListenerf(AL_GAIN, volume); diff --git a/src/modules/audio/openal/Audio.h b/src/modules/audio/openal/Audio.h index 58af10987..7c2531c4a 100644 --- a/src/modules/audio/openal/Audio.h +++ b/src/modules/audio/openal/Audio.h @@ -129,6 +129,7 @@ public: std::string getOutputDevice(); void getOutputDevices(std::vector &list); + void setOutputDevice(const char *name); private: void initializeEFX(); @@ -140,6 +141,7 @@ private: // The OpenAL context. ALCcontext *context; + std::vector attribs; // The OpenAL effects struct EffectMapStorage diff --git a/src/modules/audio/wrap_Audio.cpp b/src/modules/audio/wrap_Audio.cpp index 3ef9ab884..18a2942a8 100644 --- a/src/modules/audio/wrap_Audio.cpp +++ b/src/modules/audio/wrap_Audio.cpp @@ -568,6 +568,27 @@ int w_getOutputDevices(lua_State* L) return 1; } +int w_setOutputDevice(lua_State* L) +{ + const char *device = luaL_optstring(L, 1, nullptr); + + try + { + instance()->setOutputDevice(device); + luax_pushboolean(L, true); + return 1; + } + catch (love::Exception& e) + { + luax_pushboolean(L, false); + lua_pushstring(L, e.what()); + return 2; + } + + // To avoid compiler warning + return 0; +} + // List of functions to wrap. static const luaL_Reg functions[] = { @@ -601,6 +622,7 @@ static const luaL_Reg functions[] = { "setMixWithSystem", w_setMixWithSystem }, { "getOutputDevice", w_getOutputDevice }, { "getOutputDevices", w_getOutputDevices }, + { "setOutputDevice", w_setOutputDevice }, { 0, 0 } }; From 2cc93af436cb83a8962ecb5ff72cf8c1a4a6d3ba Mon Sep 17 00:00:00 2001 From: Miku AuahDark Date: Tue, 3 May 2022 20:39:32 +0800 Subject: [PATCH 03/18] Add love.audiodisconnected callback. The callback is fired when the audio has been disconnected. --- src/modules/audio/openal/Audio.cpp | 3 +-- src/modules/audio/openal/Pool.cpp | 33 ++++++++++++++++++++++++++++-- src/modules/audio/openal/Pool.h | 8 +++++++- src/modules/love/callbacks.lua | 5 +++++ 4 files changed, 44 insertions(+), 5 deletions(-) diff --git a/src/modules/audio/openal/Audio.cpp b/src/modules/audio/openal/Audio.cpp index 88c4a4aef..5aff7115d 100644 --- a/src/modules/audio/openal/Audio.cpp +++ b/src/modules/audio/openal/Audio.cpp @@ -179,7 +179,7 @@ Audio::Audio() try { - pool = new Pool(); + pool = new Pool(device); } catch (love::Exception &) { @@ -354,7 +354,6 @@ void Audio::getOutputDevices(std::vector &list) void Audio::setOutputDevice(const char* name) { -#undef ALC_SOFT_reopen_device #ifndef ALC_SOFT_reopen_device typedef ALCboolean (ALC_APIENTRY*LPALCREOPENDEVICESOFT)(ALCdevice *device, const ALCchar *deviceName, const ALCint *attribs); diff --git a/src/modules/audio/openal/Pool.cpp b/src/modules/audio/openal/Pool.cpp index 47020dd9f..1c1df9cc0 100644 --- a/src/modules/audio/openal/Pool.cpp +++ b/src/modules/audio/openal/Pool.cpp @@ -20,6 +20,7 @@ #include "Pool.h" +#include "event/Event.h" #include "Source.h" namespace love @@ -29,8 +30,10 @@ namespace audio namespace openal { -Pool::Pool() - : sources() +Pool::Pool(ALCdevice *device) + : device(device) + , sources() + , disconnectNotified(false) , totalSources(0) { // Clear errors. @@ -101,8 +104,34 @@ bool Pool::isPlaying(Source *s) void Pool::update() { +#ifndef ALC_CONNECTED + constexpr ALCenum ALC_CONNECTED = 0x313; +#endif + thread::Lock lock(mutex); + static bool disconnectExtSupported = alcIsExtensionPresent(device, "ALC_EXT_Disconnect") == ALC_TRUE; + + // Device disconnection event + if (disconnectExtSupported) + { + auto eventModule = Module::getInstance(Module::M_EVENT); + if (eventModule) + { + ALCint connected; + alcGetIntegerv(device, ALC_CONNECTED, 1, &connected); + + if (connected) + disconnectNotified = false; + else if (!disconnectNotified) + { + StrongRef msg(new event::Message("audiodisconnected"), Acquire::NORETAIN); + eventModule->push(msg); + disconnectNotified = true; + } + } + } + std::vector torelease; for (const auto &i : playing) diff --git a/src/modules/audio/openal/Pool.h b/src/modules/audio/openal/Pool.h index c31882c2d..278cfb4e4 100644 --- a/src/modules/audio/openal/Pool.h +++ b/src/modules/audio/openal/Pool.h @@ -64,7 +64,7 @@ class Pool { public: - Pool(); + Pool(ALCdevice *device); ~Pool(); /** @@ -101,9 +101,15 @@ private: // Maximum possible number of OpenAL sources the pool attempts to generate. static const int MAX_SOURCES = 64; + // Current OpenAL device + ALCdevice *device; + // OpenAL sources ALuint sources[MAX_SOURCES]; + // Is device disconnection has been notified? + bool disconnectNotified; + // Total number of created sources in the pool. int totalSources; diff --git a/src/modules/love/callbacks.lua b/src/modules/love/callbacks.lua index 79b1b3e97..a13cc33e8 100644 --- a/src/modules/love/callbacks.lua +++ b/src/modules/love/callbacks.lua @@ -129,6 +129,11 @@ function love.createhandlers() localechanged = function () if love.localechanged then return love.localechanged() end end, + audiodisconnected = function () + if not love.audiodisconnected or not love.audiodisconnected() then + love.audio.setOutputDevice() + end + end, }, { __index = function(self, name) error("Unknown event: " .. name) From b2a6abdc0a63c293abed43e591c70664f175536a Mon Sep 17 00:00:00 2001 From: Miku AuahDark Date: Sat, 7 May 2022 10:50:10 +0800 Subject: [PATCH 04/18] Pass all stopped sources on love.audiodisconnected callback. The behavior of sources re-played during these times is inconsistent. Streaming sources will desync while static sources stop completely. --- src/modules/audio/openal/Pool.cpp | 21 ++++++++++++++++++++- src/modules/love/callbacks.lua | 4 ++-- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/modules/audio/openal/Pool.cpp b/src/modules/audio/openal/Pool.cpp index 1c1df9cc0..667cf67b4 100644 --- a/src/modules/audio/openal/Pool.cpp +++ b/src/modules/audio/openal/Pool.cpp @@ -30,6 +30,16 @@ namespace audio namespace openal { +static Variant::SharedTable *putSourcesAsSharedTable(std::vector &sources) +{ + Variant::SharedTable *table = new Variant::SharedTable(); + + for (int i = 0; i < sources.size(); i++) + table->pairs.emplace_back((double) (i + 1), Variant(&Source::type, sources[i])); + + return table; +} + Pool::Pool(ALCdevice *device) : device(device) , sources() @@ -125,8 +135,17 @@ void Pool::update() disconnectNotified = false; else if (!disconnectNotified) { - StrongRef msg(new event::Message("audiodisconnected"), Acquire::NORETAIN); + // Get all sources in this Pool then stop it + // since they're all internally stopped. + std::vector sources = getPlayingSources(); + Source::stop(sources); + + std::vector vargs; + vargs.emplace_back(putSourcesAsSharedTable(sources)); + + StrongRef msg(new event::Message("audiodisconnected", vargs), Acquire::NORETAIN); eventModule->push(msg); + disconnectNotified = true; } } diff --git a/src/modules/love/callbacks.lua b/src/modules/love/callbacks.lua index a13cc33e8..872f21344 100644 --- a/src/modules/love/callbacks.lua +++ b/src/modules/love/callbacks.lua @@ -129,8 +129,8 @@ function love.createhandlers() localechanged = function () if love.localechanged then return love.localechanged() end end, - audiodisconnected = function () - if not love.audiodisconnected or not love.audiodisconnected() then + audiodisconnected = function (sources) + if not love.audiodisconnected or not love.audiodisconnected(sources) then love.audio.setOutputDevice() end end, From 7af537a417cf961d3174bb8f3eee9f370b2b64ed Mon Sep 17 00:00:00 2001 From: Miku AuahDark Date: Sat, 7 May 2022 11:04:28 +0800 Subject: [PATCH 05/18] Rename *OutputDevice(s) to *PlaybackDevice(s) --- src/modules/audio/Audio.cpp | 2 +- src/modules/audio/Audio.h | 12 ++++++------ src/modules/audio/null/Audio.cpp | 4 ++-- src/modules/audio/null/Audio.h | 4 ++-- src/modules/audio/openal/Audio.cpp | 8 ++++---- src/modules/audio/openal/Audio.h | 6 +++--- src/modules/audio/wrap_Audio.cpp | 18 +++++++++--------- src/modules/love/callbacks.lua | 2 +- 8 files changed, 28 insertions(+), 28 deletions(-) diff --git a/src/modules/audio/Audio.cpp b/src/modules/audio/Audio.cpp index 645c95bb6..b892556c3 100644 --- a/src/modules/audio/Audio.cpp +++ b/src/modules/audio/Audio.cpp @@ -78,7 +78,7 @@ bool Audio::setMixWithSystem(bool mix) #endif } -void Audio::setOutputDevice(const char */*name*/) +void Audio::setPlaybackDevice(const char */*name*/) { throw love::Exception("Re-setting output device is not supported."); } diff --git a/src/modules/audio/Audio.h b/src/modules/audio/Audio.h index 474d8a939..2cdc40b4e 100644 --- a/src/modules/audio/Audio.h +++ b/src/modules/audio/Audio.h @@ -298,19 +298,19 @@ public: virtual void resumeContext() = 0; /** - * Get current output device name. + * Get current playback device name. */ - virtual std::string getOutputDevice() = 0; + virtual std::string getPlaybackDevice() = 0; /** - * Retrieve list of available output devices. + * Retrieve list of available playback devices. */ - virtual void getOutputDevices(std::vector &list) = 0; + virtual void getPlaybackDevices(std::vector &list) = 0; /** - * Set the output device to specified device name. + * Set the current playback device to specified device name. */ - virtual void setOutputDevice(const char *name); + virtual void setPlaybackDevice(const char *name); private: diff --git a/src/modules/audio/null/Audio.cpp b/src/modules/audio/null/Audio.cpp index 70babb0f7..4704b2461 100644 --- a/src/modules/audio/null/Audio.cpp +++ b/src/modules/audio/null/Audio.cpp @@ -211,12 +211,12 @@ void Audio::resumeContext() { } -std::string Audio::getOutputDevice() +std::string Audio::getPlaybackDevice() { return ""; } -void Audio::getOutputDevices(std::vector &/*list*/) +void Audio::getPlaybackDevices(std::vector &/*list*/) { } diff --git a/src/modules/audio/null/Audio.h b/src/modules/audio/null/Audio.h index 11689d151..b452433f9 100644 --- a/src/modules/audio/null/Audio.h +++ b/src/modules/audio/null/Audio.h @@ -89,8 +89,8 @@ public: void pauseContext(); void resumeContext(); - std::string getOutputDevice(); - void getOutputDevices(std::vector &list); + std::string getPlaybackDevice(); + void getPlaybackDevices(std::vector &list); private: float volume; diff --git a/src/modules/audio/openal/Audio.cpp b/src/modules/audio/openal/Audio.cpp index 5aff7115d..d6208c310 100644 --- a/src/modules/audio/openal/Audio.cpp +++ b/src/modules/audio/openal/Audio.cpp @@ -328,7 +328,7 @@ void Audio::resumeContext() alcMakeContextCurrent(context); } -std::string Audio::getOutputDevice() +std::string Audio::getPlaybackDevice() { const char *dev = getDeviceSpecifier(device); @@ -338,7 +338,7 @@ std::string Audio::getOutputDevice() return dev; } -void Audio::getOutputDevices(std::vector &list) +void Audio::getPlaybackDevices(std::vector &list) { const char *devices = getDeviceSpecifier(nullptr); @@ -352,7 +352,7 @@ void Audio::getOutputDevices(std::vector &list) } } -void Audio::setOutputDevice(const char* name) +void Audio::setPlaybackDevice(const char* name) { #ifndef ALC_SOFT_reopen_device typedef ALCboolean (ALC_APIENTRY*LPALCREOPENDEVICESOFT)(ALCdevice *device, @@ -366,7 +366,7 @@ void Audio::setOutputDevice(const char* name) { // Default implementation throws exception. To make // error message consistent, call the base class. - love::audio::Audio::setOutputDevice(name); + love::audio::Audio::setPlaybackDevice(name); return; } diff --git a/src/modules/audio/openal/Audio.h b/src/modules/audio/openal/Audio.h index 7c2531c4a..e452f9867 100644 --- a/src/modules/audio/openal/Audio.h +++ b/src/modules/audio/openal/Audio.h @@ -127,9 +127,9 @@ public: bool getEffectID(const char *name, ALuint &id); - std::string getOutputDevice(); - void getOutputDevices(std::vector &list); - void setOutputDevice(const char *name); + std::string getPlaybackDevice(); + void getPlaybackDevices(std::vector &list); + void setPlaybackDevice(const char *name); private: void initializeEFX(); diff --git a/src/modules/audio/wrap_Audio.cpp b/src/modules/audio/wrap_Audio.cpp index 18a2942a8..957b11abd 100644 --- a/src/modules/audio/wrap_Audio.cpp +++ b/src/modules/audio/wrap_Audio.cpp @@ -543,20 +543,20 @@ int w_setMixWithSystem(lua_State *L) return 1; } -int w_getOutputDevice(lua_State* L) +int w_getPlaybackDevice(lua_State* L) { std::string device; - luax_catchexcept(L, [&]() { device = instance()->getOutputDevice(); }); + luax_catchexcept(L, [&]() { device = instance()->getPlaybackDevice(); }); luax_pushstring(L, device); return 1; } -int w_getOutputDevices(lua_State* L) +int w_getPlaybackDevices(lua_State* L) { std::vector list; - luax_catchexcept(L, [&]() { instance()->getOutputDevices(list); }); + luax_catchexcept(L, [&]() { instance()->getPlaybackDevices(list); }); lua_createtable(L, 0, (int) list.size()); for (int i = 0; i < (int) list.size(); i++) { @@ -568,13 +568,13 @@ int w_getOutputDevices(lua_State* L) return 1; } -int w_setOutputDevice(lua_State* L) +int w_setPlaybackDevice(lua_State* L) { const char *device = luaL_optstring(L, 1, nullptr); try { - instance()->setOutputDevice(device); + instance()->setPlaybackDevice(device); luax_pushboolean(L, true); return 1; } @@ -620,9 +620,9 @@ static const luaL_Reg functions[] = { "getMaxSourceEffects", w_getMaxSourceEffects }, { "isEffectsSupported", w_isEffectsSupported }, { "setMixWithSystem", w_setMixWithSystem }, - { "getOutputDevice", w_getOutputDevice }, - { "getOutputDevices", w_getOutputDevices }, - { "setOutputDevice", w_setOutputDevice }, + { "getPlaybackDevice", w_getPlaybackDevice }, + { "getPlaybackDevices", w_getPlaybackDevices }, + { "setPlaybackDevice", w_setPlaybackDevice }, { 0, 0 } }; diff --git a/src/modules/love/callbacks.lua b/src/modules/love/callbacks.lua index 872f21344..b0532a346 100644 --- a/src/modules/love/callbacks.lua +++ b/src/modules/love/callbacks.lua @@ -131,7 +131,7 @@ function love.createhandlers() end, audiodisconnected = function (sources) if not love.audiodisconnected or not love.audiodisconnected(sources) then - love.audio.setOutputDevice() + love.audio.setPlaybackDevice() end end, }, { From e79c27214079e3892b2018e0db9aa2712ec92e17 Mon Sep 17 00:00:00 2001 From: Miku AuahDark Date: Sat, 7 May 2022 12:18:24 +0800 Subject: [PATCH 06/18] Update dr_flac to 0.12.38 Fix buzzing audio in some cases when decoding FLAC in Ogg container. --- license.txt | 2 +- src/libraries/dr/dr_flac.h | 248 +++++++++++++++++++++++++------------ 2 files changed, 172 insertions(+), 78 deletions(-) diff --git a/license.txt b/license.txt index f0f948234..929bfac48 100644 --- a/license.txt +++ b/license.txt @@ -88,7 +88,7 @@ This distribution contains code from the following projects (full license text b - dr_flac Website: https://github.com/mackron/dr_libs - Source download: https://github.com/mackron/dr_libs/blob/c5e5355/dr_flac.h + Source download: https://github.com/mackron/dr_libs/blob/15f37e3/dr_flac.h License: MIT/Expat Copyright 2018 David Reid diff --git a/src/libraries/dr/dr_flac.h b/src/libraries/dr/dr_flac.h index ed31a1e01..5328a8e98 100644 --- a/src/libraries/dr/dr_flac.h +++ b/src/libraries/dr/dr_flac.h @@ -1,6 +1,6 @@ /* FLAC audio decoder. Choice of public domain or MIT-0. See license statements at the end of this file. -dr_flac - v0.12.33 - 2021-12-22 +dr_flac - v0.12.38 - 2022-04-10 David Reid - mackron@gmail.com @@ -232,7 +232,7 @@ extern "C" { #define DRFLAC_VERSION_MAJOR 0 #define DRFLAC_VERSION_MINOR 12 -#define DRFLAC_VERSION_REVISION 33 +#define DRFLAC_VERSION_REVISION 38 #define DRFLAC_VERSION_STRING DRFLAC_XSTRINGIFY(DRFLAC_VERSION_MAJOR) "." DRFLAC_XSTRINGIFY(DRFLAC_VERSION_MINOR) "." DRFLAC_XSTRINGIFY(DRFLAC_VERSION_REVISION) #include /* For size_t. */ @@ -1363,9 +1363,15 @@ DRFLAC_API drflac_bool32 drflac_next_cuesheet_track(drflac_cuesheet_track_iterat I am using "__inline__" only when we're compiling in strict ANSI mode. */ #if defined(__STRICT_ANSI__) - #define DRFLAC_INLINE __inline__ __attribute__((always_inline)) + #define DRFLAC_GNUC_INLINE_HINT __inline__ #else - #define DRFLAC_INLINE inline __attribute__((always_inline)) + #define DRFLAC_GNUC_INLINE_HINT inline + #endif + + #if (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 2)) || defined(__clang__) + #define DRFLAC_INLINE DRFLAC_GNUC_INLINE_HINT __attribute__((always_inline)) + #else + #define DRFLAC_INLINE DRFLAC_GNUC_INLINE_HINT #endif #elif defined(__WATCOMC__) #define DRFLAC_INLINE __inline @@ -1378,7 +1384,7 @@ DRFLAC_API drflac_bool32 drflac_next_cuesheet_track(drflac_cuesheet_track_iterat #define DRFLAC_X64 #elif defined(__i386) || defined(_M_IX86) #define DRFLAC_X86 -#elif defined(__arm__) || defined(_M_ARM) || defined(_M_ARM64) +#elif defined(__arm__) || defined(_M_ARM) || defined(__arm64) || defined(__arm64__) || defined(__aarch64__) || defined(_M_ARM64) #define DRFLAC_ARM #endif @@ -1431,16 +1437,6 @@ Unfortuantely dr_flac depends on this for a few things so we're just going to di #if defined(DRFLAC_ARM) #if !defined(DRFLAC_NO_NEON) && (defined(__ARM_NEON) || defined(__aarch64__) || defined(_M_ARM64)) #define DRFLAC_SUPPORT_NEON - #endif - - /* Fall back to looking for the #include file. */ - #if !defined(__GNUC__) && !defined(__clang__) && defined(__has_include) - #if !defined(DRFLAC_SUPPORT_NEON) && !defined(DRFLAC_NO_NEON) && __has_include() - #define DRFLAC_SUPPORT_NEON - #endif - #endif - - #if defined(DRFLAC_SUPPORT_NEON) #include #endif #endif @@ -1909,6 +1905,12 @@ static DRFLAC_INLINE drflac_uint32 drflac__be2host_32(drflac_uint32 n) return n; } +static DRFLAC_INLINE drflac_uint32 drflac__be2host_32_ptr_unaligned(const void* pData) +{ + const drflac_uint8* pNum = (drflac_uint8*)pData; + return *(pNum) << 24 | *(pNum+1) << 16 | *(pNum+2) << 8 | *(pNum+3); +} + static DRFLAC_INLINE drflac_uint64 drflac__be2host_64(drflac_uint64 n) { if (drflac__is_little_endian()) { @@ -1928,6 +1930,12 @@ static DRFLAC_INLINE drflac_uint32 drflac__le2host_32(drflac_uint32 n) return n; } +static DRFLAC_INLINE drflac_uint32 drflac__le2host_32_ptr_unaligned(const void* pData) +{ + const drflac_uint8* pNum = (drflac_uint8*)pData; + return *pNum | *(pNum+1) << 8 | *(pNum+2) << 16 | *(pNum+3) << 24; +} + static DRFLAC_INLINE drflac_uint32 drflac__unsynchsafe_32(drflac_uint32 n) { @@ -2429,6 +2437,10 @@ static DRFLAC_INLINE drflac_bool32 drflac__read_uint32(drflac_bs* bs, unsigned i if (!drflac__reload_cache(bs)) { return DRFLAC_FALSE; } + if (bitCountLo > DRFLAC_CACHE_L1_BITS_REMAINING(bs)) { + /* This happens when we get to end of stream */ + return DRFLAC_FALSE; + } *pResultOut = (resultHi << bitCountLo) | (drflac_uint32)DRFLAC_CACHE_L1_SELECT_AND_SHIFT(bs, bitCountLo); bs->consumedBits += bitCountLo; @@ -2872,9 +2884,24 @@ static DRFLAC_INLINE drflac_bool32 drflac__seek_past_next_set_bit(drflac_bs* bs, } } + if (bs->cache == 1) { + /* Not catching this would lead to undefined behaviour: a shift of a 32-bit number by 32 or more is undefined */ + *pOffsetOut = zeroCounter + (drflac_uint32)DRFLAC_CACHE_L1_BITS_REMAINING(bs) - 1; + if (!drflac__reload_cache(bs)) { + return DRFLAC_FALSE; + } + + return DRFLAC_TRUE; + } + setBitOffsetPlus1 = drflac__clz(bs->cache); setBitOffsetPlus1 += 1; + if (setBitOffsetPlus1 > DRFLAC_CACHE_L1_BITS_REMAINING(bs)) { + /* This happens when we get to end of stream */ + return DRFLAC_FALSE; + } + bs->consumedBits += setBitOffsetPlus1; bs->cache <<= setBitOffsetPlus1; @@ -2989,6 +3016,25 @@ static drflac_result drflac__read_utf8_coded_number(drflac_bs* bs, drflac_uint64 } +static DRFLAC_INLINE drflac_uint32 drflac__ilog2_u32(drflac_uint32 x) +{ +#if 1 /* Needs optimizing. */ + drflac_uint32 result = 0; + while (x > 0) { + result += 1; + x >>= 1; + } + + return result; +#endif +} + +static DRFLAC_INLINE drflac_bool32 drflac__use_64_bit_prediction(drflac_uint32 bitsPerSample, drflac_uint32 order, drflac_uint32 precision) +{ + /* https://web.archive.org/web/20220205005724/https://github.com/ietf-wg-cellar/flac-specification/blob/37a49aa48ba4ba12e8757badfc59c0df35435fec/rfc_backmatter.md */ + return bitsPerSample + precision + drflac__ilog2_u32(order) > 32; +} + /* The next two functions are responsible for calculating the prediction. @@ -2996,6 +3042,9 @@ The next two functions are responsible for calculating the prediction. When the bits per sample is >16 we need to use 64-bit integer arithmetic because otherwise we'll run out of precision. It's safe to assume this will be slower on 32-bit platforms so we use a more optimal solution when the bits per sample is <=16. */ +#if defined(__clang__) +__attribute__((no_sanitize("signed-integer-overflow"))) +#endif static DRFLAC_INLINE drflac_int32 drflac__calculate_prediction_32(drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pDecodedSamples) { drflac_int32 prediction = 0; @@ -3231,7 +3280,7 @@ static DRFLAC_INLINE drflac_int32 drflac__calculate_prediction_64(drflac_uint32 Reference implementation for reading and decoding samples with residual. This is intentionally left unoptimized for the sake of readability and should only be used as a reference. */ -static drflac_bool32 drflac__decode_samples_with_residual__rice__reference(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pSamplesOut) +static drflac_bool32 drflac__decode_samples_with_residual__rice__reference(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 lpcOrder, drflac_int32 lpcShift, drflac_uint32 lpcPrecision, const drflac_int32* coefficients, drflac_int32* pSamplesOut) { drflac_uint32 i; @@ -3270,10 +3319,10 @@ static drflac_bool32 drflac__decode_samples_with_residual__rice__reference(drfla } - if (bitsPerSample+shift >= 32) { - pSamplesOut[i] = decodedRice + drflac__calculate_prediction_64(order, shift, coefficients, pSamplesOut + i); + if (drflac__use_64_bit_prediction(bitsPerSample, lpcOrder, lpcPrecision)) { + pSamplesOut[i] = decodedRice + drflac__calculate_prediction_64(lpcOrder, lpcShift, coefficients, pSamplesOut + i); } else { - pSamplesOut[i] = decodedRice + drflac__calculate_prediction_32(order, shift, coefficients, pSamplesOut + i); + pSamplesOut[i] = decodedRice + drflac__calculate_prediction_32(lpcOrder, lpcShift, coefficients, pSamplesOut + i); } } @@ -3370,6 +3419,10 @@ static DRFLAC_INLINE drflac_bool32 drflac__read_rice_parts(drflac_bs* bs, drflac if (!drflac__reload_cache(bs)) { return DRFLAC_FALSE; } + if (bitCountLo > DRFLAC_CACHE_L1_BITS_REMAINING(bs)) { + /* This happens when we get to end of stream */ + return DRFLAC_FALSE; + } } riceParamPart = (drflac_uint32)(resultHi | DRFLAC_CACHE_L1_SELECT_AND_SHIFT_SAFE(bs, bitCountLo)); @@ -3450,6 +3503,10 @@ static DRFLAC_INLINE drflac_bool32 drflac__read_rice_parts_x1(drflac_bs* bs, drf if (!drflac__reload_cache(bs)) { return DRFLAC_FALSE; } + if (riceParamPartLoBitCount > DRFLAC_CACHE_L1_BITS_REMAINING(bs)) { + /* This happens when we get to end of stream */ + return DRFLAC_FALSE; + } bs_cache = bs->cache; bs_consumedBits = bs->consumedBits + riceParamPartLoBitCount; @@ -3560,6 +3617,11 @@ static DRFLAC_INLINE drflac_bool32 drflac__seek_rice_parts(drflac_bs* bs, drflac return DRFLAC_FALSE; } + if (riceParamPartLoBitCount > DRFLAC_CACHE_L1_BITS_REMAINING(bs)) { + /* This happens when we get to end of stream */ + return DRFLAC_FALSE; + } + bs_cache = bs->cache; bs_consumedBits = bs->consumedBits + riceParamPartLoBitCount; } @@ -3646,7 +3708,7 @@ static drflac_bool32 drflac__decode_samples_with_residual__rice__scalar_zeroorde return DRFLAC_TRUE; } -static drflac_bool32 drflac__decode_samples_with_residual__rice__scalar(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pSamplesOut) +static drflac_bool32 drflac__decode_samples_with_residual__rice__scalar(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 lpcOrder, drflac_int32 lpcShift, drflac_uint32 lpcPrecision, const drflac_int32* coefficients, drflac_int32* pSamplesOut) { drflac_uint32 t[2] = {0x00000000, 0xFFFFFFFF}; drflac_uint32 zeroCountPart0 = 0; @@ -3664,14 +3726,14 @@ static drflac_bool32 drflac__decode_samples_with_residual__rice__scalar(drflac_b DRFLAC_ASSERT(bs != NULL); DRFLAC_ASSERT(pSamplesOut != NULL); - if (order == 0) { - return drflac__decode_samples_with_residual__rice__scalar_zeroorder(bs, bitsPerSample, count, riceParam, order, shift, coefficients, pSamplesOut); + if (lpcOrder == 0) { + return drflac__decode_samples_with_residual__rice__scalar_zeroorder(bs, bitsPerSample, count, riceParam, lpcOrder, lpcShift, coefficients, pSamplesOut); } riceParamMask = (drflac_uint32)~((~0UL) << riceParam); pSamplesOutEnd = pSamplesOut + (count & ~3); - if (bitsPerSample+shift > 32) { + if (drflac__use_64_bit_prediction(bitsPerSample, lpcOrder, lpcPrecision)) { while (pSamplesOut < pSamplesOutEnd) { /* Rice extraction. It's faster to do this one at a time against local variables than it is to use the x4 version @@ -3699,10 +3761,10 @@ static drflac_bool32 drflac__decode_samples_with_residual__rice__scalar(drflac_b riceParamPart2 = (riceParamPart2 >> 1) ^ t[riceParamPart2 & 0x01]; riceParamPart3 = (riceParamPart3 >> 1) ^ t[riceParamPart3 & 0x01]; - pSamplesOut[0] = riceParamPart0 + drflac__calculate_prediction_64(order, shift, coefficients, pSamplesOut + 0); - pSamplesOut[1] = riceParamPart1 + drflac__calculate_prediction_64(order, shift, coefficients, pSamplesOut + 1); - pSamplesOut[2] = riceParamPart2 + drflac__calculate_prediction_64(order, shift, coefficients, pSamplesOut + 2); - pSamplesOut[3] = riceParamPart3 + drflac__calculate_prediction_64(order, shift, coefficients, pSamplesOut + 3); + pSamplesOut[0] = riceParamPart0 + drflac__calculate_prediction_64(lpcOrder, lpcShift, coefficients, pSamplesOut + 0); + pSamplesOut[1] = riceParamPart1 + drflac__calculate_prediction_64(lpcOrder, lpcShift, coefficients, pSamplesOut + 1); + pSamplesOut[2] = riceParamPart2 + drflac__calculate_prediction_64(lpcOrder, lpcShift, coefficients, pSamplesOut + 2); + pSamplesOut[3] = riceParamPart3 + drflac__calculate_prediction_64(lpcOrder, lpcShift, coefficients, pSamplesOut + 3); pSamplesOut += 4; } @@ -3730,10 +3792,10 @@ static drflac_bool32 drflac__decode_samples_with_residual__rice__scalar(drflac_b riceParamPart2 = (riceParamPart2 >> 1) ^ t[riceParamPart2 & 0x01]; riceParamPart3 = (riceParamPart3 >> 1) ^ t[riceParamPart3 & 0x01]; - pSamplesOut[0] = riceParamPart0 + drflac__calculate_prediction_32(order, shift, coefficients, pSamplesOut + 0); - pSamplesOut[1] = riceParamPart1 + drflac__calculate_prediction_32(order, shift, coefficients, pSamplesOut + 1); - pSamplesOut[2] = riceParamPart2 + drflac__calculate_prediction_32(order, shift, coefficients, pSamplesOut + 2); - pSamplesOut[3] = riceParamPart3 + drflac__calculate_prediction_32(order, shift, coefficients, pSamplesOut + 3); + pSamplesOut[0] = riceParamPart0 + drflac__calculate_prediction_32(lpcOrder, lpcShift, coefficients, pSamplesOut + 0); + pSamplesOut[1] = riceParamPart1 + drflac__calculate_prediction_32(lpcOrder, lpcShift, coefficients, pSamplesOut + 1); + pSamplesOut[2] = riceParamPart2 + drflac__calculate_prediction_32(lpcOrder, lpcShift, coefficients, pSamplesOut + 2); + pSamplesOut[3] = riceParamPart3 + drflac__calculate_prediction_32(lpcOrder, lpcShift, coefficients, pSamplesOut + 3); pSamplesOut += 4; } @@ -3753,10 +3815,10 @@ static drflac_bool32 drflac__decode_samples_with_residual__rice__scalar(drflac_b /*riceParamPart0 = (riceParamPart0 >> 1) ^ (~(riceParamPart0 & 0x01) + 1);*/ /* Sample reconstruction. */ - if (bitsPerSample+shift > 32) { - pSamplesOut[0] = riceParamPart0 + drflac__calculate_prediction_64(order, shift, coefficients, pSamplesOut + 0); + if (drflac__use_64_bit_prediction(bitsPerSample, lpcOrder, lpcPrecision)) { + pSamplesOut[0] = riceParamPart0 + drflac__calculate_prediction_64(lpcOrder, lpcShift, coefficients, pSamplesOut + 0); } else { - pSamplesOut[0] = riceParamPart0 + drflac__calculate_prediction_32(order, shift, coefficients, pSamplesOut + 0); + pSamplesOut[0] = riceParamPart0 + drflac__calculate_prediction_32(lpcOrder, lpcShift, coefficients, pSamplesOut + 0); } i += 1; @@ -4212,20 +4274,20 @@ static drflac_bool32 drflac__decode_samples_with_residual__rice__sse41_64(drflac return DRFLAC_TRUE; } -static drflac_bool32 drflac__decode_samples_with_residual__rice__sse41(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pSamplesOut) +static drflac_bool32 drflac__decode_samples_with_residual__rice__sse41(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 lpcOrder, drflac_int32 lpcShift, drflac_uint32 lpcPrecision, const drflac_int32* coefficients, drflac_int32* pSamplesOut) { DRFLAC_ASSERT(bs != NULL); DRFLAC_ASSERT(pSamplesOut != NULL); /* In my testing the order is rarely > 12, so in this case I'm going to simplify the SSE implementation by only handling order <= 12. */ - if (order > 0 && order <= 12) { - if (bitsPerSample+shift > 32) { - return drflac__decode_samples_with_residual__rice__sse41_64(bs, count, riceParam, order, shift, coefficients, pSamplesOut); + if (lpcOrder > 0 && lpcOrder <= 12) { + if (drflac__use_64_bit_prediction(bitsPerSample, lpcOrder, lpcPrecision)) { + return drflac__decode_samples_with_residual__rice__sse41_64(bs, count, riceParam, lpcOrder, lpcShift, coefficients, pSamplesOut); } else { - return drflac__decode_samples_with_residual__rice__sse41_32(bs, count, riceParam, order, shift, coefficients, pSamplesOut); + return drflac__decode_samples_with_residual__rice__sse41_32(bs, count, riceParam, lpcOrder, lpcShift, coefficients, pSamplesOut); } } else { - return drflac__decode_samples_with_residual__rice__scalar(bs, bitsPerSample, count, riceParam, order, shift, coefficients, pSamplesOut); + return drflac__decode_samples_with_residual__rice__scalar(bs, bitsPerSample, count, riceParam, lpcOrder, lpcShift, lpcPrecision, coefficients, pSamplesOut); } } #endif @@ -4562,7 +4624,7 @@ static drflac_bool32 drflac__decode_samples_with_residual__rice__neon_64(drflac_ /* Pre-loading the coefficients and prior samples is annoying because we need to ensure we don't try reading more than - what's available in the input buffers. It would be conenient to use a fall-through switch to do this, but this results + what's available in the input buffers. It would be convenient to use a fall-through switch to do this, but this results in strict aliasing warnings with GCC. To work around this I'm just doing something hacky. This feels a bit convoluted so I think there's opportunity for this to be simplified. */ @@ -4710,41 +4772,41 @@ static drflac_bool32 drflac__decode_samples_with_residual__rice__neon_64(drflac_ return DRFLAC_TRUE; } -static drflac_bool32 drflac__decode_samples_with_residual__rice__neon(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pSamplesOut) +static drflac_bool32 drflac__decode_samples_with_residual__rice__neon(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 lpcOrder, drflac_int32 lpcShift, drflac_uint32 lpcPrecision, const drflac_int32* coefficients, drflac_int32* pSamplesOut) { DRFLAC_ASSERT(bs != NULL); DRFLAC_ASSERT(pSamplesOut != NULL); /* In my testing the order is rarely > 12, so in this case I'm going to simplify the NEON implementation by only handling order <= 12. */ - if (order > 0 && order <= 12) { - if (bitsPerSample+shift > 32) { - return drflac__decode_samples_with_residual__rice__neon_64(bs, count, riceParam, order, shift, coefficients, pSamplesOut); + if (lpcOrder > 0 && lpcOrder <= 12) { + if (drflac__use_64_bit_prediction(bitsPerSample, lpcOrder, lpcPrecision)) { + return drflac__decode_samples_with_residual__rice__neon_64(bs, count, riceParam, lpcOrder, lpcShift, coefficients, pSamplesOut); } else { - return drflac__decode_samples_with_residual__rice__neon_32(bs, count, riceParam, order, shift, coefficients, pSamplesOut); + return drflac__decode_samples_with_residual__rice__neon_32(bs, count, riceParam, lpcOrder, lpcShift, coefficients, pSamplesOut); } } else { - return drflac__decode_samples_with_residual__rice__scalar(bs, bitsPerSample, count, riceParam, order, shift, coefficients, pSamplesOut); + return drflac__decode_samples_with_residual__rice__scalar(bs, bitsPerSample, count, riceParam, lpcOrder, lpcShift, lpcPrecision, coefficients, pSamplesOut); } } #endif -static drflac_bool32 drflac__decode_samples_with_residual__rice(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pSamplesOut) +static drflac_bool32 drflac__decode_samples_with_residual__rice(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 lpcOrder, drflac_int32 lpcShift, drflac_uint32 lpcPrecision, const drflac_int32* coefficients, drflac_int32* pSamplesOut) { #if defined(DRFLAC_SUPPORT_SSE41) if (drflac__gIsSSE41Supported) { - return drflac__decode_samples_with_residual__rice__sse41(bs, bitsPerSample, count, riceParam, order, shift, coefficients, pSamplesOut); + return drflac__decode_samples_with_residual__rice__sse41(bs, bitsPerSample, count, riceParam, lpcOrder, lpcShift, lpcPrecision, coefficients, pSamplesOut); } else #elif defined(DRFLAC_SUPPORT_NEON) if (drflac__gIsNEONSupported) { - return drflac__decode_samples_with_residual__rice__neon(bs, bitsPerSample, count, riceParam, order, shift, coefficients, pSamplesOut); + return drflac__decode_samples_with_residual__rice__neon(bs, bitsPerSample, count, riceParam, lpcOrder, lpcShift, lpcPrecision, coefficients, pSamplesOut); } else #endif { /* Scalar fallback. */ #if 0 - return drflac__decode_samples_with_residual__rice__reference(bs, bitsPerSample, count, riceParam, order, shift, coefficients, pSamplesOut); + return drflac__decode_samples_with_residual__rice__reference(bs, bitsPerSample, count, riceParam, lpcOrder, lpcShift, lpcPrecision, coefficients, pSamplesOut); #else - return drflac__decode_samples_with_residual__rice__scalar(bs, bitsPerSample, count, riceParam, order, shift, coefficients, pSamplesOut); + return drflac__decode_samples_with_residual__rice__scalar(bs, bitsPerSample, count, riceParam, lpcOrder, lpcShift, lpcPrecision, coefficients, pSamplesOut); #endif } } @@ -4765,7 +4827,10 @@ static drflac_bool32 drflac__read_and_seek_residual__rice(drflac_bs* bs, drflac_ return DRFLAC_TRUE; } -static drflac_bool32 drflac__decode_samples_with_residual__unencoded(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 unencodedBitsPerSample, drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pSamplesOut) +#if defined(__clang__) +__attribute__((no_sanitize("signed-integer-overflow"))) +#endif +static drflac_bool32 drflac__decode_samples_with_residual__unencoded(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 unencodedBitsPerSample, drflac_uint32 lpcOrder, drflac_int32 lpcShift, drflac_uint32 lpcPrecision, const drflac_int32* coefficients, drflac_int32* pSamplesOut) { drflac_uint32 i; @@ -4782,10 +4847,10 @@ static drflac_bool32 drflac__decode_samples_with_residual__unencoded(drflac_bs* pSamplesOut[i] = 0; } - if (bitsPerSample >= 24) { - pSamplesOut[i] += drflac__calculate_prediction_64(order, shift, coefficients, pSamplesOut + i); + if (drflac__use_64_bit_prediction(bitsPerSample, lpcOrder, lpcPrecision)) { + pSamplesOut[i] += drflac__calculate_prediction_64(lpcOrder, lpcShift, coefficients, pSamplesOut + i); } else { - pSamplesOut[i] += drflac__calculate_prediction_32(order, shift, coefficients, pSamplesOut + i); + pSamplesOut[i] += drflac__calculate_prediction_32(lpcOrder, lpcShift, coefficients, pSamplesOut + i); } } @@ -4798,7 +4863,7 @@ Reads and decodes the residual for the sub-frame the decoder is currently sittin when the decoder is sitting at the very start of the RESIDUAL block. The first residuals will be ignored. The and parameters are used to determine how many residual values need to be decoded. */ -static drflac_bool32 drflac__decode_samples_with_residual(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 blockSize, drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pDecodedSamples) +static drflac_bool32 drflac__decode_samples_with_residual(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 blockSize, drflac_uint32 lpcOrder, drflac_int32 lpcShift, drflac_uint32 lpcPrecision, const drflac_int32* coefficients, drflac_int32* pDecodedSamples) { drflac_uint8 residualMethod; drflac_uint8 partitionOrder; @@ -4818,7 +4883,7 @@ static drflac_bool32 drflac__decode_samples_with_residual(drflac_bs* bs, drflac_ } /* Ignore the first values. */ - pDecodedSamples += order; + pDecodedSamples += lpcOrder; if (!drflac__read_uint8(bs, 4, &partitionOrder)) { return DRFLAC_FALSE; @@ -4833,11 +4898,11 @@ static drflac_bool32 drflac__decode_samples_with_residual(drflac_bs* bs, drflac_ } /* Validation check. */ - if ((blockSize / (1 << partitionOrder)) < order) { + if ((blockSize / (1 << partitionOrder)) < lpcOrder) { return DRFLAC_FALSE; } - samplesInPartition = (blockSize / (1 << partitionOrder)) - order; + samplesInPartition = (blockSize / (1 << partitionOrder)) - lpcOrder; partitionsRemaining = (1 << partitionOrder); for (;;) { drflac_uint8 riceParam = 0; @@ -4858,7 +4923,7 @@ static drflac_bool32 drflac__decode_samples_with_residual(drflac_bs* bs, drflac_ } if (riceParam != 0xFF) { - if (!drflac__decode_samples_with_residual__rice(bs, bitsPerSample, samplesInPartition, riceParam, order, shift, coefficients, pDecodedSamples)) { + if (!drflac__decode_samples_with_residual__rice(bs, bitsPerSample, samplesInPartition, riceParam, lpcOrder, lpcShift, lpcPrecision, coefficients, pDecodedSamples)) { return DRFLAC_FALSE; } } else { @@ -4867,7 +4932,7 @@ static drflac_bool32 drflac__decode_samples_with_residual(drflac_bs* bs, drflac_ return DRFLAC_FALSE; } - if (!drflac__decode_samples_with_residual__unencoded(bs, bitsPerSample, samplesInPartition, unencodedBitsPerSample, order, shift, coefficients, pDecodedSamples)) { + if (!drflac__decode_samples_with_residual__unencoded(bs, bitsPerSample, samplesInPartition, unencodedBitsPerSample, lpcOrder, lpcShift, lpcPrecision, coefficients, pDecodedSamples)) { return DRFLAC_FALSE; } } @@ -5036,7 +5101,7 @@ static drflac_bool32 drflac__decode_samples__fixed(drflac_bs* bs, drflac_uint32 pDecodedSamples[i] = sample; } - if (!drflac__decode_samples_with_residual(bs, subframeBitsPerSample, blockSize, lpcOrder, 0, lpcCoefficientsTable[lpcOrder], pDecodedSamples)) { + if (!drflac__decode_samples_with_residual(bs, subframeBitsPerSample, blockSize, lpcOrder, 0, 4, lpcCoefficientsTable[lpcOrder], pDecodedSamples)) { return DRFLAC_FALSE; } @@ -5091,7 +5156,7 @@ static drflac_bool32 drflac__decode_samples__lpc(drflac_bs* bs, drflac_uint32 bl } } - if (!drflac__decode_samples_with_residual(bs, bitsPerSample, blockSize, lpcOrder, lpcShift, coefficients, pDecodedSamples)) { + if (!drflac__decode_samples_with_residual(bs, bitsPerSample, blockSize, lpcOrder, lpcShift, lpcPrecision, coefficients, pDecodedSamples)) { return DRFLAC_FALSE; } @@ -5219,6 +5284,9 @@ static drflac_bool32 drflac__read_next_flac_frame_header(drflac_bs* bs, drflac_u return DRFLAC_FALSE; } crc8 = drflac_crc8(crc8, header->blockSizeInPCMFrames, 16); + if (header->blockSizeInPCMFrames == 0xFFFF) { + return DRFLAC_FALSE; /* Frame is too big. This is the size of the frame minus 1. The STREAMINFO block defines the max block size which is 16-bits. Adding one will make it 17 bits and therefore too big. */ + } header->blockSizeInPCMFrames += 1; } else { DRFLAC_ASSERT(blockSize >= 8); @@ -5257,6 +5325,11 @@ static drflac_bool32 drflac__read_next_flac_frame_header(drflac_bs* bs, drflac_u header->bitsPerSample = streaminfoBitsPerSample; } + if (header->bitsPerSample != streaminfoBitsPerSample) { + /* If this subframe has a different bitsPerSample then streaminfo or the first frame, reject it */ + return DRFLAC_FALSE; + } + if (!drflac__read_uint8(bs, 8, &header->crc8)) { return DRFLAC_FALSE; } @@ -5343,6 +5416,11 @@ static drflac_bool32 drflac__decode_subframe(drflac_bs* bs, drflac_frame* frame, subframeBitsPerSample += 1; } + if (subframeBitsPerSample > 32) { + /* libFLAC and ffmpeg reject 33-bit subframes as well */ + return DRFLAC_FALSE; + } + /* Need to handle wasted bits per sample. */ if (pSubframe->wastedBitsPerSample >= subframeBitsPerSample) { return DRFLAC_FALSE; @@ -6485,7 +6563,7 @@ static drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, d pRunningData = (const char*)pRawData; pRunningDataEnd = (const char*)pRawData + blockSize; - metadata.data.vorbis_comment.vendorLength = drflac__le2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4; + metadata.data.vorbis_comment.vendorLength = drflac__le2host_32_ptr_unaligned(pRunningData); pRunningData += 4; /* Need space for the rest of the block */ if ((pRunningDataEnd - pRunningData) - 4 < (drflac_int64)metadata.data.vorbis_comment.vendorLength) { /* <-- Note the order of operations to avoid overflow to a valid value */ @@ -6493,7 +6571,7 @@ static drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, d return DRFLAC_FALSE; } metadata.data.vorbis_comment.vendor = pRunningData; pRunningData += metadata.data.vorbis_comment.vendorLength; - metadata.data.vorbis_comment.commentCount = drflac__le2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4; + metadata.data.vorbis_comment.commentCount = drflac__le2host_32_ptr_unaligned(pRunningData); pRunningData += 4; /* Need space for 'commentCount' comments after the block, which at minimum is a drflac_uint32 per comment */ if ((pRunningDataEnd - pRunningData) / sizeof(drflac_uint32) < metadata.data.vorbis_comment.commentCount) { /* <-- Note the order of operations to avoid overflow to a valid value */ @@ -6511,7 +6589,7 @@ static drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, d return DRFLAC_FALSE; } - commentLength = drflac__le2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4; + commentLength = drflac__le2host_32_ptr_unaligned(pRunningData); pRunningData += 4; if (pRunningDataEnd - pRunningData < (drflac_int64)commentLength) { /* <-- Note the order of operations to avoid overflow to a valid value */ drflac__free_from_callbacks(pRawData, pAllocationCallbacks); return DRFLAC_FALSE; @@ -6620,8 +6698,8 @@ static drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, d pRunningData = (const char*)pRawData; pRunningDataEnd = (const char*)pRawData + blockSize; - metadata.data.picture.type = drflac__be2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4; - metadata.data.picture.mimeLength = drflac__be2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4; + metadata.data.picture.type = drflac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4; + metadata.data.picture.mimeLength = drflac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4; /* Need space for the rest of the block */ if ((pRunningDataEnd - pRunningData) - 24 < (drflac_int64)metadata.data.picture.mimeLength) { /* <-- Note the order of operations to avoid overflow to a valid value */ @@ -6629,7 +6707,7 @@ static drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, d return DRFLAC_FALSE; } metadata.data.picture.mime = pRunningData; pRunningData += metadata.data.picture.mimeLength; - metadata.data.picture.descriptionLength = drflac__be2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4; + metadata.data.picture.descriptionLength = drflac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4; /* Need space for the rest of the block */ if ((pRunningDataEnd - pRunningData) - 20 < (drflac_int64)metadata.data.picture.descriptionLength) { /* <-- Note the order of operations to avoid overflow to a valid value */ @@ -6637,11 +6715,11 @@ static drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, d return DRFLAC_FALSE; } metadata.data.picture.description = pRunningData; pRunningData += metadata.data.picture.descriptionLength; - metadata.data.picture.width = drflac__be2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4; - metadata.data.picture.height = drflac__be2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4; - metadata.data.picture.colorDepth = drflac__be2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4; - metadata.data.picture.indexColorCount = drflac__be2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4; - metadata.data.picture.pictureDataSize = drflac__be2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4; + metadata.data.picture.width = drflac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4; + metadata.data.picture.height = drflac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4; + metadata.data.picture.colorDepth = drflac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4; + metadata.data.picture.indexColorCount = drflac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4; + metadata.data.picture.pictureDataSize = drflac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4; metadata.data.picture.pPictureData = (const drflac_uint8*)pRunningData; /* Need space for the picture after the block */ @@ -7865,7 +7943,7 @@ static drflac* drflac_open_with_metadata_private(drflac_read_proc onRead, drflac #ifndef DR_FLAC_NO_OGG if (init.container == drflac_container_ogg) { drflac_oggbs* pInternalOggbs = (drflac_oggbs*)((drflac_uint8*)pFlac->pDecodedSamples + decodedSamplesAllocationSize + seektableSize); - *pInternalOggbs = oggbs; + DRFLAC_COPY_MEMORY(pInternalOggbs, &oggbs, sizeof(oggbs)); /* The Ogg bistream needs to be layered on top of the original bitstream. */ pFlac->bs.onRead = drflac__on_read_ogg; @@ -11786,7 +11864,7 @@ DRFLAC_API const char* drflac_next_vorbis_comment(drflac_vorbis_comment_iterator return NULL; } - length = drflac__le2host_32(*(const drflac_uint32*)pIter->pRunningData); + length = drflac__le2host_32_ptr_unaligned(pIter->pRunningData); pIter->pRunningData += 4; pComment = pIter->pRunningData; @@ -11856,6 +11934,22 @@ DRFLAC_API drflac_bool32 drflac_next_cuesheet_track(drflac_cuesheet_track_iterat /* REVISION HISTORY ================ +v0.12.38 - 2022-04-10 + - Fix compilation error on older versions of GCC. + +v0.12.37 - 2022-02-12 + - Improve ARM detection. + +v0.12.36 - 2022-02-07 + - Fix a compilation error with the ARM build. + +v0.12.35 - 2022-02-06 + - Fix a bug due to underestimating the amount of precision required for the prediction stage. + - Fix some bugs found from fuzz testing. + +v0.12.34 - 2022-01-07 + - Fix some misalignment bugs when reading metadata. + v0.12.33 - 2021-12-22 - Fix a bug with seeking when the seek table does not start at PCM frame 0. From 201697260728211dc4c751cb30dfc84cd37aa897 Mon Sep 17 00:00:00 2001 From: Miku AuahDark Date: Sun, 22 May 2022 20:20:39 +0800 Subject: [PATCH 07/18] Android: Reorder fused file preference. --- src/common/android.cpp | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/common/android.cpp b/src/common/android.cpp index a5118f25c..ffa0a9ae5 100644 --- a/src/common/android.cpp +++ b/src/common/android.cpp @@ -83,7 +83,7 @@ double getScreenScale() JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv(); jclass activity = env->FindClass("org/love2d/android/GameActivity"); - jmethodID getMetrics = env->GetStaticMethodID(activity, "getMetrics", "()Landroid/util/DisplayMetrics;"); + jmethodID getMetrics = env->GetStaticMethodID(activity, "getDisplayMetrics", "()Landroid/util/DisplayMetrics;"); jobject metrics = env->CallStaticObjectMethod(activity, getMetrics); jclass metricsClass = env->GetObjectClass(metrics); @@ -102,7 +102,7 @@ bool getSafeArea(int &top, int &left, int &bottom, int &right) JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv(); jobject activity = (jobject) SDL_AndroidGetActivity(); jclass clazz(env->GetObjectClass(activity)); - jmethodID methodID = env->GetMethodID(clazz, "initializeSafeArea", "()Z"); + jmethodID methodID = env->GetMethodID(clazz, "getSafeArea", "()Z"); bool hasSafeArea = false; if (methodID == nullptr) @@ -755,27 +755,25 @@ void deinitializeVirtualArchive() bool checkFusedGame(void **physfsIO_Out) { - // TODO: Reorder the loading in 12.0 PHYSFS_Io *&io = *(PHYSFS_Io **) physfsIO_Out; AAssetManager *assetManager = getAssetManager(); - // Prefer game.love inside assets/ folder - AAsset *asset = AAssetManager_open(assetManager, "game.love", AASSET_MODE_RANDOM); - if (asset) - { - io = aasset::io::fromAAsset(assetManager, "game.love", asset); - return true; - } - - // If there's no game.love inside assets/ try main.lua - asset = AAssetManager_open(assetManager, "main.lua", AASSET_MODE_STREAMING); - + // Prefer main.lua inside assets/ folder + AAsset *asset = AAssetManager_open(assetManager, "main.lua", AASSET_MODE_STREAMING); if (asset) { AAsset_close(asset); io = nullptr; return true; } + + // If there's no main.lua inside assets/ try game.love + asset = AAssetManager_open(assetManager, "game.love", AASSET_MODE_RANDOM); + if (asset) + { + io = aasset::io::fromAAsset(assetManager, "game.love", asset); + return true; + } // Not found return false; From aaeb318aa6b3718f6b1d079c3b0191081a74d40b Mon Sep 17 00:00:00 2001 From: Miku AuahDark Date: Sun, 22 May 2022 22:40:09 +0800 Subject: [PATCH 08/18] Android: Implement special path URI for passing file descriptors. This is to workaround the limitation of content:// URI severely limited. --- src/common/android.cpp | 180 ++++++++++++------- src/common/android.h | 21 ++- src/modules/filesystem/physfs/Filesystem.cpp | 36 ++-- 3 files changed, 144 insertions(+), 93 deletions(-) diff --git a/src/common/android.cpp b/src/common/android.cpp index ffa0a9ae5..08fd2338c 100644 --- a/src/common/android.cpp +++ b/src/common/android.cpp @@ -19,6 +19,7 @@ **/ #include "android.h" +#include "Object.h" #ifdef LOVE_ANDROID @@ -122,34 +123,6 @@ bool getSafeArea(int &top, int &left, int &bottom, int &right) return hasSafeArea; } -const char *getSelectedGameFile() -{ - static const char *path = NULL; - - if (path) - { - delete path; - path = NULL; - } - - JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv(); - jclass activity = env->FindClass("org/love2d/android/GameActivity"); - - jmethodID getGamePath = env->GetStaticMethodID(activity, "getGamePath", "()Ljava/lang/String;"); - jstring gamePath = (jstring) env->CallStaticObjectMethod(activity, getGamePath); - const char *utf = env->GetStringUTFChars(gamePath, 0); - if (utf) - { - path = SDL_strdup(utf); - env->ReleaseStringUTFChars(gamePath, utf); - } - - env->DeleteLocalRef(gamePath); - env->DeleteLocalRef(activity); - - return path; -} - bool openURL(const std::string &url) { JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv(); @@ -192,37 +165,6 @@ void freeGameArchiveMemory(void *ptr) delete[] game_love_data; } -bool loadGameArchiveToMemory(const char* filename, char **ptr, size_t *size) -{ - SDL_RWops *asset_game_file = SDL_RWFromFile(filename, "rb"); - if (!asset_game_file) { - SDL_Log("Could not find %s", filename); - return false; - } - - Sint64 file_size = asset_game_file->size(asset_game_file); - if (file_size <= 0) { - SDL_Log("Could not load game from %s. File has invalid file size: %d.", filename, (int) file_size); - return false; - } - - *ptr = new char[file_size]; - if (!*ptr) { - SDL_Log("Could not allocate memory for in-memory game archive"); - return false; - } - - size_t bytes_copied = asset_game_file->read(asset_game_file, (void*) *ptr, sizeof(char), (size_t) file_size); - if (bytes_copied != file_size) { - SDL_Log("Incomplete copy of in-memory game archive!"); - delete[] *ptr; - return false; - } - - *size = (size_t) file_size; - return true; -} - bool directoryExists(const char *path) { struct stat s; @@ -766,7 +708,7 @@ bool checkFusedGame(void **physfsIO_Out) io = nullptr; return true; } - + // If there's no main.lua inside assets/ try game.love asset = AAssetManager_open(assetManager, "game.love", AASSET_MODE_RANDOM); if (asset) @@ -821,6 +763,124 @@ const char *getCRequirePath() return path; } +int getFDFromLoveProtocol(const char *path) +{ + constexpr const char PROTOCOL[] = "love2d://fd/"; + constexpr size_t PROTOCOL_LEN = sizeof(PROTOCOL) - 1; + + if (*path == '/') + path++; + + if (memcmp(path, PROTOCOL, PROTOCOL_LEN) == 0) + { + try + { + return std::stoi(path + PROTOCOL_LEN, nullptr, 10); + } + catch (std::logic_error &) + { } + } + + return -1; +} + +class FileDescriptorTracker: public love::Object +{ +public: + FileDescriptorTracker(int fd): Object(), fd(fd) {} + ~FileDescriptorTracker() { close(fd); } + int getFd() { return fd; } +private: + int fd; +}; + +struct FileDescriptorIO +{ + FileDescriptorTracker *fd; + off64_t size; + off64_t offset; +}; + +void *getIOFromFD(int fd) +{ + if (fd == -1) + return nullptr; + + // Create file descriptor IO structure + FileDescriptorIO *fdio = new FileDescriptorIO(); + fdio->size = lseek64(fd, 0, SEEK_END); + fdio->offset = 0; + lseek64(fd, 0, SEEK_SET); + + if (fdio->size == -1) + { + // Cannot get size + delete fdio; + return nullptr; + } + + fdio->fd = new FileDescriptorTracker(fd); + + PHYSFS_Io *io = new PHYSFS_Io(); + io->version = 0; + io->opaque = fdio; + io->read = [](PHYSFS_Io *io, void *buf, PHYSFS_uint64 size) + { + FileDescriptorIO *fdio = (FileDescriptorIO *) io->opaque; + ssize_t ret = pread64(fdio->fd->getFd(), buf, (size_t) size, fdio->offset); + + if (ret == -1) + PHYSFS_setErrorCode(PHYSFS_ERR_OTHER_ERROR); + else + fdio->offset = std::min(fdio->offset + (off64_t) ret, fdio->size); + + return (PHYSFS_sint64) ret; + }; + io->write = nullptr; + io->seek = [](PHYSFS_Io *io, PHYSFS_uint64 offset) + { + FileDescriptorIO *fdio = (FileDescriptorIO *) io->opaque; + fdio->offset = std::min(std::max((off64_t) offset, 0), fdio->size); + // Always success + return 1; + }; + io->tell = [](PHYSFS_Io *io) + { + FileDescriptorIO *fdio = (FileDescriptorIO *) io->opaque; + return (PHYSFS_sint64) fdio->offset; + }; + io->length = [](PHYSFS_Io *io) + { + FileDescriptorIO *fdio = (FileDescriptorIO *) io->opaque; + return (PHYSFS_sint64) fdio->size; + }; + io->duplicate = [](PHYSFS_Io *io) + { + FileDescriptorIO *fdio = (FileDescriptorIO *) io->opaque; + FileDescriptorIO *fdio2 = new FileDescriptorIO(); + PHYSFS_Io *io2 = new PHYSFS_Io(); + + fdio->fd->retain(); + + // Copy data + *fdio2 = *fdio; + *io2 = *io; + io2->opaque = fdio2; + + return io2; + }; + io->flush = nullptr; + io->destroy = [](PHYSFS_Io *io) + { + FileDescriptorIO *fdio = (FileDescriptorIO *) io->opaque; + fdio->fd->release(); + delete fdio; + delete io; + }; + + return io; +} + } // android } // love diff --git a/src/common/android.h b/src/common/android.h index 543a94a9e..25eb1621e 100644 --- a/src/common/android.h +++ b/src/common/android.h @@ -50,11 +50,6 @@ double getScreenScale(); **/ bool getSafeArea(int &top, int &left, int &bottom, int &right); -/** - * Gets the selected love file in the device filesystem. - **/ -const char *getSelectedGameFile(); - bool openURL(const std::string &url); void vibrate(double seconds); @@ -64,8 +59,6 @@ void vibrate(double seconds); */ void freeGameArchiveMemory(void *ptr); -bool loadGameArchiveToMemory(const char *filename, char **ptr, size_t *size); - bool directoryExists(const char *path); bool mkdir(const char *path); @@ -103,6 +96,20 @@ bool checkFusedGame(void **physfsIO_Out); const char *getCRequirePath(); +/** + * Attempt to parse "(/)love2d://fd/" from path. + * @param path Potentially special path. + * @return File descriptor passed if successful, -1 if path is not valid. + */ +int getFDFromLoveProtocol(const char *path); + +/** + * Create PHYSFS_Io from file descriptor. + * @param fd File descriptor + * @return PHYSFS_Io casted to void*. + */ +void *getIOFromFD(int fd); + } // android } // love diff --git a/src/modules/filesystem/physfs/Filesystem.cpp b/src/modules/filesystem/physfs/Filesystem.cpp index 27f58e826..a548f2f49 100644 --- a/src/modules/filesystem/physfs/Filesystem.cpp +++ b/src/modules/filesystem/physfs/Filesystem.cpp @@ -236,8 +236,6 @@ bool Filesystem::setSource(const char *source) if (!love::android::createStorageDirectories()) SDL_Log("Error creating storage directories!"); - new_search_path = ""; - PHYSFS_Io *gameLoveIO; bool hasFusedGame = love::android::checkFusedGame((void **) &gameLoveIO); bool isAAssetMounted = false; @@ -263,38 +261,24 @@ bool Filesystem::setSource(const char *source) if (!isAAssetMounted) { - new_search_path = love::android::getSelectedGameFile(); - - // try mounting first, if that fails, load to memory and mount - if (!PHYSFS_mount(new_search_path.c_str(), nullptr, 1)) + // Is this love2d://fd/ URIs? + int fd = love::android::getFDFromLoveProtocol(new_search_path.c_str()); + if (fd != -1) { - // PHYSFS cannot yet mount a zip file inside an .apk - SDL_Log("Mounting %s did not work. Loading to memory.", - new_search_path.c_str()); - char* game_archive_ptr = NULL; - size_t game_archive_size = 0; - if (!love::android::loadGameArchiveToMemory( - new_search_path.c_str(), &game_archive_ptr, - &game_archive_size)) + PHYSFS_Io *io = (PHYSFS_Io *) love::android::getIOFromFD(fd); + + if (PHYSFS_mountIo(io, "LOVE.FD", nullptr, 0)) { - SDL_Log("Failure memory loading archive %s", new_search_path.c_str()); - return false; - } - if (!PHYSFS_mountMemory( - game_archive_ptr, game_archive_size, - love::android::freeGameArchiveMemory, "archive.zip", "/", 0)) - { - SDL_Log("Failure mounting in-memory archive."); - love::android::freeGameArchiveMemory(game_archive_ptr); - return false; + gameSource = new_search_path; + return true; } } } -#else +#endif + // Add the directory. if (!PHYSFS_mount(new_search_path.c_str(), nullptr, 1)) return false; -#endif // Save the game source. gameSource = new_search_path; From 2822f17a399e14abfafe21aa94e4db0dd61369d4 Mon Sep 17 00:00:00 2001 From: Miku AuahDark Date: Sat, 28 May 2022 13:47:53 +0800 Subject: [PATCH 09/18] Minor improvements and fixes to Android-specific code. --- src/common/android.cpp | 108 +++++++++++++++++------------------------ 1 file changed, 45 insertions(+), 63 deletions(-) diff --git a/src/common/android.cpp b/src/common/android.cpp index 08fd2338c..105ac31be 100644 --- a/src/common/android.cpp +++ b/src/common/android.cpp @@ -46,13 +46,11 @@ namespace android void setImmersive(bool immersive_active) { JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv(); - jobject activity = (jobject) SDL_AndroidGetActivity(); + jclass clazz = env->GetObjectClass(activity); - jclass clazz(env->GetObjectClass(activity)); - jmethodID method_id = env->GetMethodID(clazz, "setImmersiveMode", "(Z)V"); - - env->CallVoidMethod(activity, method_id, immersive_active); + static jmethodID setImmersiveMethod = env->GetMethodID(clazz, "setImmersiveMode", "(Z)V"); + env->CallVoidMethod(activity, setImmersiveMethod, immersive_active); env->DeleteLocalRef(activity); env->DeleteLocalRef(clazz); @@ -61,18 +59,16 @@ void setImmersive(bool immersive_active) bool getImmersive() { JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv(); - jobject activity = (jobject) SDL_AndroidGetActivity(); + jclass clazz = env->GetObjectClass(activity); - jclass clazz(env->GetObjectClass(activity)); - jmethodID method_id = env->GetMethodID(clazz, "getImmersiveMode", "()Z"); - - jboolean immersive_active = env->CallBooleanMethod(activity, method_id); + static jmethodID getImmersiveMethod = env->GetMethodID(clazz, "getImmersiveMode", "()Z"); + jboolean immersiveActive = env->CallBooleanMethod(activity, getImmersiveMethod); env->DeleteLocalRef(activity); env->DeleteLocalRef(clazz); - return immersive_active; + return immersiveActive; } double getScreenScale() @@ -82,16 +78,13 @@ double getScreenScale() if (result == -1.) { JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv(); - jclass activity = env->FindClass("org/love2d/android/GameActivity"); + jobject activity = (jobject) SDL_AndroidGetActivity(); + jclass clazz = env->GetObjectClass(activity); - jmethodID getMetrics = env->GetStaticMethodID(activity, "getDisplayMetrics", "()Landroid/util/DisplayMetrics;"); - jobject metrics = env->CallStaticObjectMethod(activity, getMetrics); - jclass metricsClass = env->GetObjectClass(metrics); + jmethodID getDPIMethod = env->GetMethodID(clazz, "getDPIScale", "()F"); + result = (double) env->CallFloatMethod(activity, getDPIMethod); - result = env->GetFloatField(metrics, env->GetFieldID(metricsClass, "density", "F")); - - env->DeleteLocalRef(metricsClass); - env->DeleteLocalRef(metrics); + env->DeleteLocalRef(clazz); env->DeleteLocalRef(activity); } @@ -102,8 +95,10 @@ bool getSafeArea(int &top, int &left, int &bottom, int &right) { JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv(); jobject activity = (jobject) SDL_AndroidGetActivity(); - jclass clazz(env->GetObjectClass(activity)); - jmethodID methodID = env->GetMethodID(clazz, "getSafeArea", "()Z"); + jclass clazz = env->GetObjectClass(activity); + + static jmethodID methodID = env->GetMethodID(clazz, "getSafeArea", "()Z"); + bool hasSafeArea = false; if (methodID == nullptr) @@ -126,33 +121,33 @@ bool getSafeArea(int &top, int &left, int &bottom, int &right) bool openURL(const std::string &url) { JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv(); - jclass activity = env->FindClass("org/love2d/android/GameActivity"); + jobject activity = (jobject) SDL_AndroidGetActivity(); + jclass clazz = env->GetObjectClass(activity); - jmethodID openURL = env->GetStaticMethodID(activity, "openURLFromLOVE", "(Ljava/lang/String;)Z"); + static jmethodID openURL = env->GetMethodID(clazz, "openURLFromLOVE", "(Ljava/lang/String;)Z"); if (openURL == nullptr) - { - env->ExceptionClear(); - openURL = env->GetStaticMethodID(activity, "openURL", "(Ljava/lang/String;)Z"); - } + return false; - jstring url_jstring = (jstring) env->NewStringUTF(url.c_str()); + jstring jstringURL = env->NewStringUTF(url.c_str()); + jboolean result = env->CallBooleanMethod(clazz, openURL, jstringURL); - jboolean result = env->CallStaticBooleanMethod(activity, openURL, url_jstring); - - env->DeleteLocalRef(url_jstring); + env->DeleteLocalRef(jstringURL); + env->DeleteLocalRef(clazz); env->DeleteLocalRef(activity); - return result; + return (bool) result; } void vibrate(double seconds) { JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv(); - jclass activity = env->FindClass("org/love2d/android/GameActivity"); + jobject activity = (jobject) SDL_AndroidGetActivity(); + jclass clazz = env->GetObjectClass(activity); - jmethodID vibrate_method = env->GetStaticMethodID(activity, "vibrate", "(D)V"); - env->CallStaticVoidMethod(activity, vibrate_method, seconds); + static jmethodID vibrateMethod = env->GetMethodID(clazz, "vibrate", "(D)V"); + env->CallVoidMethod(activity, vibrateMethod, seconds); + env->DeleteLocalRef(clazz); env->DeleteLocalRef(activity); } @@ -167,7 +162,7 @@ void freeGameArchiveMemory(void *ptr) bool directoryExists(const char *path) { - struct stat s; + struct stat s {}; int err = stat(path, &s); if (err == -1) { @@ -226,9 +221,9 @@ bool hasRecordingPermission() { JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv(); jobject activity = (jobject) SDL_AndroidGetActivity(); + jclass clazz = env->GetObjectClass(activity); - jclass clazz(env->GetObjectClass(activity)); - jmethodID methodID = env->GetMethodID(clazz, "hasRecordAudioPermission", "()Z"); + static jmethodID methodID = env->GetMethodID(clazz, "hasRecordAudioPermission", "()Z"); jboolean result = false; if (methodID == nullptr) @@ -724,7 +719,7 @@ bool checkFusedGame(void **physfsIO_Out) const char *getCRequirePath() { static bool initialized = false; - static const char *path = nullptr; + static std::string path; if (!initialized) { @@ -732,35 +727,22 @@ const char *getCRequirePath() jobject activity = (jobject) SDL_AndroidGetActivity(); jclass clazz(env->GetObjectClass(activity)); - jmethodID method_id = env->GetMethodID(clazz, "getCRequirePath", "()Ljava/lang/String;"); + jmethodID getCRequireMethod = env->GetMethodID(clazz, "getCRequirePath", "()Ljava/lang/String;"); - path = ""; - initialized = true; - - if (method_id) + jstring cpath = (jstring) env->CallObjectMethod(activity, getCRequireMethod); + const char *utf = env->GetStringUTFChars(cpath, nullptr); + if (utf) { - jstring cpath = (jstring) env->CallObjectMethod(activity, method_id); - const char *utf = env->GetStringUTFChars(cpath, nullptr); - if (utf) - { - path = SDL_strdup(utf); - env->ReleaseStringUTFChars(cpath, utf); - } - - env->DeleteLocalRef(cpath); - } - else - { - // NoSuchMethodException is thrown in case methodID is null - env->ExceptionClear(); - return ""; + path = utf; + env->ReleaseStringUTFChars(cpath, utf); } + env->DeleteLocalRef(cpath); env->DeleteLocalRef(activity); env->DeleteLocalRef(clazz); } - return path; + return path.c_str(); } int getFDFromLoveProtocol(const char *path) @@ -787,9 +769,9 @@ int getFDFromLoveProtocol(const char *path) class FileDescriptorTracker: public love::Object { public: - FileDescriptorTracker(int fd): Object(), fd(fd) {} - ~FileDescriptorTracker() { close(fd); } - int getFd() { return fd; } + explicit FileDescriptorTracker(int fd): Object(), fd(fd) {} + ~FileDescriptorTracker() override { close(fd); } + int getFd() const { return fd; } private: int fd; }; From 9e1e35ff3916850d67e2271922ffef5cb17393d0 Mon Sep 17 00:00:00 2001 From: Miku AuahDark Date: Sat, 28 May 2022 19:17:50 +0800 Subject: [PATCH 10/18] Fixed love.audio.newSource(SoundData) variant. --- src/modules/audio/wrap_Audio.cpp | 45 +++++++++++++++++--------------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/src/modules/audio/wrap_Audio.cpp b/src/modules/audio/wrap_Audio.cpp index 957b11abd..32f8c24bf 100644 --- a/src/modules/audio/wrap_Audio.cpp +++ b/src/modules/audio/wrap_Audio.cpp @@ -48,32 +48,35 @@ int w_newSource(lua_State *L) { Source::Type stype = Source::TYPE_STREAM; - if (!luax_istype(L, 1, love::sound::SoundData::type) && !luax_istype(L, 1, love::sound::Decoder::type)) + if (!luax_istype(L, 1, love::sound::SoundData::type)) { - const char *stypestr = luaL_checkstring(L, 2); - if (stypestr && !Source::getConstant(stypestr, stype)) - return luax_enumerror(L, "source type", Source::getConstants(stype), stypestr); + if (!luax_istype(L, 1, love::sound::Decoder::type)) + { + const char *stypestr = luaL_checkstring(L, 2); + if (stypestr && !Source::getConstant(stypestr, stype)) + return luax_enumerror(L, "source type", Source::getConstants(stype), stypestr); - if (stype == Source::TYPE_QUEUE) - return luaL_error(L, "Cannot create queueable sources using newSource. Use newQueueableSource instead."); - } + if (stype == Source::TYPE_QUEUE) + return luaL_error(L, "Cannot create queueable sources using newSource. Use newQueueableSource instead."); + } - if (love::filesystem::luax_cangetdata(L, 1)) - { - // stream type - if (stype == Source::TYPE_STATIC) - lua_pushstring(L, "memory"); - else if (!lua_isnone(L, 3)) - lua_pushvalue(L, 3); - else + if (love::filesystem::luax_cangetdata(L, 1)) + { + // stream type + if (stype == Source::TYPE_STATIC) + lua_pushstring(L, "memory"); + else if (!lua_isnone(L, 3)) + lua_pushvalue(L, 3); + else + lua_pushnil(L); + + // buffer size lua_pushnil(L); - // buffer size - lua_pushnil(L); - - // (file, buffer size, stream type) - int idxs[] = { 1, lua_gettop(L), lua_gettop(L) - 1 }; - luax_convobj(L, idxs, 3, "sound", "newDecoder"); + // (file, buffer size, stream type) + int idxs[] = { 1, lua_gettop(L), lua_gettop(L) - 1 }; + luax_convobj(L, idxs, 3, "sound", "newDecoder"); + } } if (stype == Source::TYPE_STATIC && luax_istype(L, 1, love::sound::Decoder::type)) From 28790741776db99907806d55fcb33d509116d8fb Mon Sep 17 00:00:00 2001 From: Miku AuahDark Date: Sat, 28 May 2022 19:19:50 +0800 Subject: [PATCH 11/18] Silence compiler warnings in isModifierActive. --- src/modules/keyboard/sdl/Keyboard.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/modules/keyboard/sdl/Keyboard.cpp b/src/modules/keyboard/sdl/Keyboard.cpp index 1abb0858d..c0ee7fad4 100644 --- a/src/modules/keyboard/sdl/Keyboard.cpp +++ b/src/modules/keyboard/sdl/Keyboard.cpp @@ -99,6 +99,8 @@ bool Keyboard::isModifierActive(ModifierKey key) const return (modstate & KMOD_SCROLL) != 0; case MODKEY_MODE: return (modstate & KMOD_MODE) != 0; + default: + break; } return false; From 7b66e50cfc72e85dee2ec4e28b756aad952be80e Mon Sep 17 00:00:00 2001 From: Miku AuahDark Date: Sun, 29 May 2022 01:49:29 +0800 Subject: [PATCH 12/18] Add support for love.filedropped in Android. To send file to the filedropped callback, share the files to the game. Note that for non-fused binary, if there's no game running, LOVE treat it as *.love game to run. --- src/common/android.cpp | 30 ++++++++++++++++++++++----- src/common/android.h | 7 +++++++ src/modules/filesystem/NativeFile.cpp | 21 ++++++++++++++++++- 3 files changed, 52 insertions(+), 6 deletions(-) diff --git a/src/common/android.cpp b/src/common/android.cpp index 105ac31be..629ff0b1b 100644 --- a/src/common/android.cpp +++ b/src/common/android.cpp @@ -126,9 +126,6 @@ bool openURL(const std::string &url) static jmethodID openURL = env->GetMethodID(clazz, "openURLFromLOVE", "(Ljava/lang/String;)Z"); - if (openURL == nullptr) - return false; - jstring jstringURL = env->NewStringUTF(url.c_str()); jboolean result = env->CallBooleanMethod(clazz, openURL, jstringURL); @@ -725,9 +722,9 @@ const char *getCRequirePath() { JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv(); jobject activity = (jobject) SDL_AndroidGetActivity(); + jclass clazz = env->GetObjectClass(activity); - jclass clazz(env->GetObjectClass(activity)); - jmethodID getCRequireMethod = env->GetMethodID(clazz, "getCRequirePath", "()Ljava/lang/String;"); + static jmethodID getCRequireMethod = env->GetMethodID(clazz, "getCRequirePath", "()Ljava/lang/String;"); jstring cpath = (jstring) env->CallObjectMethod(activity, getCRequireMethod); const char *utf = env->GetStringUTFChars(cpath, nullptr); @@ -745,6 +742,29 @@ const char *getCRequirePath() return path.c_str(); } +int getFDFromContentProtocol(const char *path) +{ + int fd = -1; + + if (strstr(path, "content://") == path) + { + JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv(); + jobject activity = (jobject) SDL_AndroidGetActivity(); + jclass clazz = env->GetObjectClass(activity); + + static jmethodID converter = env->GetMethodID(clazz, "convertToFileDescriptor", "(Ljava/lang/String;)I"); + + jstring uri = env->NewStringUTF(path); + fd = (int) env->CallIntMethod(activity, converter, uri); + + env->DeleteLocalRef(uri); + env->DeleteLocalRef(clazz); + env->DeleteLocalRef(activity); + } + + return fd; +} + int getFDFromLoveProtocol(const char *path) { constexpr const char PROTOCOL[] = "love2d://fd/"; diff --git a/src/common/android.h b/src/common/android.h index 25eb1621e..24f32a27a 100644 --- a/src/common/android.h +++ b/src/common/android.h @@ -96,6 +96,13 @@ bool checkFusedGame(void **physfsIO_Out); const char *getCRequirePath(); +/** + * Convert "content://" to file descriptor. + * @param path Path with content:// URI + * @return File descriptor if successful, -1 on failure. + */ +int getFDFromContentProtocol(const char *path); + /** * Attempt to parse "(/)love2d://fd/" from path. * @param path Potentially special path. diff --git a/src/modules/filesystem/NativeFile.cpp b/src/modules/filesystem/NativeFile.cpp index 58753cfcf..d0640da52 100644 --- a/src/modules/filesystem/NativeFile.cpp +++ b/src/modules/filesystem/NativeFile.cpp @@ -22,6 +22,10 @@ #include "NativeFile.h" #include "common/utf8.h" +#ifdef LOVE_ANDROID +#include "common/android.h" +#endif + // Assume POSIX or Visual Studio. #include #include @@ -84,7 +88,22 @@ bool NativeFile::open(Mode newmode) if (file != nullptr) return false; -#ifdef LOVE_WINDOWS +#if defined(LOVE_ANDROID) + // Try to handle content:// URI + int fd = love::android::getFDFromContentProtocol(filename.c_str()); + if (fd != -1) + { + if (newmode != MODE_READ) + { + ::close(fd); + throw love::Exception("%s is read-only.", filename.c_str()); + } + + file = fdopen(fd, "rb"); + } + else + file = fopen(filename.c_str(), getModeString(newmode)); +#elif defined(LOVE_WINDOWS) // make sure non-ASCII filenames work. std::wstring modestr = to_widestr(getModeString(newmode)); std::wstring wfilename = to_widestr(filename); From a533982cb4277b9c56bb57a3201c357046ba69ac Mon Sep 17 00:00:00 2001 From: Alex Szpakowski Date: Mon, 30 May 2022 21:43:25 -0300 Subject: [PATCH 13/18] Replace internal 'staging' buffer data type with new 'readback' type. --- src/modules/graphics/Buffer.cpp | 5 ++- src/modules/graphics/Buffer.h | 9 ++-- src/modules/graphics/Graphics.cpp | 6 +++ src/modules/graphics/metal/Buffer.h | 2 +- src/modules/graphics/metal/Buffer.mm | 62 ++++++++++++++++---------- src/modules/graphics/opengl/Buffer.cpp | 43 ++++++++++++++---- src/modules/graphics/opengl/Buffer.h | 4 +- src/modules/graphics/opengl/OpenGL.cpp | 2 +- src/modules/graphics/vertex.cpp | 8 ++-- src/modules/graphics/vertex.h | 2 +- 10 files changed, 97 insertions(+), 46 deletions(-) diff --git a/src/modules/graphics/Buffer.cpp b/src/modules/graphics/Buffer.cpp index 89c3f0704..c8ab11d7b 100644 --- a/src/modules/graphics/Buffer.cpp +++ b/src/modules/graphics/Buffer.cpp @@ -36,6 +36,7 @@ Buffer::Buffer(Graphics *gfx, const Settings &settings, const std::vectorgetDataUsage() == BUFFERDATAUSAGE_STREAM) throw love::Exception("Buffers created with 'stream' data usage cannot be used as a copy destination."); + if (source->getDataUsage() == BUFFERDATAUSAGE_READBACK) + throw love::Exception("Buffers created with 'readback' data usage cannot be used as a copy source."); + if (sourcerange.getMax() >= source->getSize()) throw love::Exception("Buffer copy source offset and size doesn't fit within the source Buffer's size."); @@ -1290,6 +1293,9 @@ void Graphics::copyBufferToTexture(Buffer *source, Texture *dest, size_t sourceo if (!capabilities.features[FEATURE_COPY_BUFFER_TO_TEXTURE]) throw love::Exception("Copying a Buffer to a Texture is not supported on this system."); + if (source->getDataUsage() == BUFFERDATAUSAGE_READBACK) + throw love::Exception("Buffers created with 'readback' data usage cannot be used as a copy source."); + PixelFormat format = dest->getPixelFormat(); if (isPixelFormatDepthStencil(format)) diff --git a/src/modules/graphics/metal/Buffer.h b/src/modules/graphics/metal/Buffer.h index 440114137..40f1c64ac 100644 --- a/src/modules/graphics/metal/Buffer.h +++ b/src/modules/graphics/metal/Buffer.h @@ -40,7 +40,7 @@ public: void *map(MapType map, size_t offset, size_t size) override; void unmap(size_t usedoffset, size_t usedsize) override; - void fill(size_t offset, size_t size, const void *data) override; + bool fill(size_t offset, size_t size, const void *data) override; void copyTo(love::graphics::Buffer *dest, size_t sourceoffset, size_t destoffset, size_t size) override; ptrdiff_t getHandle() const override { return (ptrdiff_t) buffer; } diff --git a/src/modules/graphics/metal/Buffer.mm b/src/modules/graphics/metal/Buffer.mm index 07cc47f3a..bac55cb1c 100644 --- a/src/modules/graphics/metal/Buffer.mm +++ b/src/modules/graphics/metal/Buffer.mm @@ -18,7 +18,7 @@ * 3. This notice may not be removed or altered from any source distribution. **/ -#import "Buffer.h" +#include "Buffer.h" #include "Graphics.h" namespace love @@ -65,11 +65,16 @@ Buffer::Buffer(love::graphics::Graphics *gfx, id device, const Settin size = getSize(); arraylength = getArrayLength(); - MTLResourceOptions opts = MTLResourceStorageModePrivate; + MTLResourceOptions opts = 0; + if (settings.dataUsage == BUFFERDATAUSAGE_READBACK) + opts |= MTLResourceStorageModeShared; + else + opts |= MTLResourceStorageModePrivate; + buffer = [device newBufferWithLength:size options:opts]; if (buffer == nil) - throw love::Exception("Could not create buffer (out of VRAM?)"); + throw love::Exception("Could not create buffer with %d bytes (out of VRAM?)", size); if (usageFlags & BUFFERUSAGEFLAG_TEXEL) { @@ -109,16 +114,30 @@ Buffer::~Buffer() texture = nil; }} -void *Buffer::map(MapType /*map*/, size_t offset, size_t size) +void *Buffer::map(MapType map, size_t offset, size_t size) { @autoreleasepool { - if (size == 0 || isImmutable()) + if (size == 0) return nullptr; + if (map == MAP_WRITE_INVALIDATE && (isImmutable() || dataUsage == BUFFERDATAUSAGE_READBACK)) + return nullptr; + + if (map == MAP_READ_ONLY && dataUsage != BUFFERDATAUSAGE_READBACK) + return nullptr; + Range r(offset, size); if (!Range(0, getSize()).contains(r)) return nullptr; + if (map == MAP_READ_ONLY) + { + mappedRange = r; + mapped = true; + mappedType = map; + return (char *) buffer.contents + offset; + } + auto gfx = Graphics::getInstance(); // TODO: Don't create a new buffer every time, also do something for stream @@ -129,6 +148,7 @@ void *Buffer::map(MapType /*map*/, size_t offset, size_t size) { mappedRange = r; mapped = true; + mappedType = map; return mapBuffer.contents; } @@ -137,6 +157,12 @@ void *Buffer::map(MapType /*map*/, size_t offset, size_t size) void Buffer::unmap(size_t usedoffset, size_t usedsize) { @autoreleasepool { + if (mappedType == MAP_READ_ONLY) + { + mapped = false; + return; + } + if (mapBuffer == nil) return; @@ -158,29 +184,17 @@ void Buffer::unmap(size_t usedoffset, size_t usedsize) mapped = false; }} -void Buffer::fill(size_t offset, size_t size, const void *data) +bool Buffer::fill(size_t offset, size_t size, const void *data) { @autoreleasepool { - if (size == 0 || isImmutable()) - return; + void *dest = map(MAP_WRITE_INVALIDATE, offset, size); - size_t buffersize = getSize(); + if (dest == nullptr) + return false; - if (!Range(0, buffersize).contains(Range(offset, size))) - return; + memcpy(dest, data, size); - // TODO: Don't create a new buffer every time, also do something for stream - // buffers. - auto gfx = Graphics::getInstance(); - auto encoder = gfx->useBlitEncoder(); - - auto tempbuffer = [gfx->device newBufferWithLength:size options:MTLResourceStorageModeShared]; - memcpy(tempbuffer.contents, data, size); - - [encoder copyFromBuffer:tempbuffer - sourceOffset:0 - toBuffer:buffer - destinationOffset:offset - size:size]; + unmap(offset, size); + return true; }} void Buffer::copyTo(love::graphics::Buffer *dest, size_t sourceoffset, size_t destoffset, size_t size) diff --git a/src/modules/graphics/opengl/Buffer.cpp b/src/modules/graphics/opengl/Buffer.cpp index 3750312ad..3a6cf74ff 100644 --- a/src/modules/graphics/opengl/Buffer.cpp +++ b/src/modules/graphics/opengl/Buffer.cpp @@ -103,7 +103,7 @@ Buffer::Buffer(love::graphics::Graphics *gfx, const Settings &settings, const st if (!load(data)) { unloadVolatile(); - throw love::Exception("Could not create buffer (out of VRAM?)"); + throw love::Exception("Could not create buffer with %d bytes (out of VRAM?)", size); } } @@ -164,11 +164,17 @@ bool Buffer::supportsOrphan() const return dataUsage == BUFFERDATAUSAGE_STREAM || dataUsage == BUFFERDATAUSAGE_DYNAMIC; } -void *Buffer::map(MapType /*map*/, size_t offset, size_t size) +void *Buffer::map(MapType map, size_t offset, size_t size) { - if (size == 0 || isImmutable()) + if (size == 0) return nullptr; + if (map == MAP_WRITE_INVALIDATE && (isImmutable() || dataUsage == BUFFERDATAUSAGE_READBACK)) + return nullptr; + + if (map == MAP_READ_ONLY && dataUsage != BUFFERDATAUSAGE_READBACK) + return nullptr; + Range r(offset, size); if (!Range(0, getSize()).contains(r)) @@ -176,7 +182,16 @@ void *Buffer::map(MapType /*map*/, size_t offset, size_t size) char *data = nullptr; - if (ownsMemoryMap) + if (map == MAP_READ_ONLY) + { + gl.bindBuffer(mapUsage, buffer); + + if (GLAD_VERSION_3_0 || GLAD_ES_VERSION_3_0) + data = (char *) glMapBufferRange(target, offset, size, GL_MAP_READ_BIT); + else if (GLAD_VERSION_1_1) + data = (char *) glMapBuffer(target, GL_READ_ONLY) + offset; + } + else if (ownsMemoryMap) { if (memoryMap == nullptr) memoryMap = (char *) malloc(getSize()); @@ -191,6 +206,7 @@ void *Buffer::map(MapType /*map*/, size_t offset, size_t size) if (data != nullptr) { mapped = true; + mappedType = map; mappedRange = r; if (!ownsMemoryMap) memoryMap = data; @@ -208,6 +224,15 @@ void Buffer::unmap(size_t usedoffset, size_t usedsize) mapped = false; + if (mappedType == MAP_READ_ONLY) + { + gl.bindBuffer(mapUsage, buffer); + glUnmapBuffer(target); + if (!ownsMemoryMap) + memoryMap = nullptr; + return; + } + // Orphan optimization - see fill(). if (supportsOrphan() && mappedRange.first == 0 && mappedRange.getSize() == getSize()) { @@ -227,15 +252,15 @@ void Buffer::unmap(size_t usedoffset, size_t usedsize) } } -void Buffer::fill(size_t offset, size_t size, const void *data) +bool Buffer::fill(size_t offset, size_t size, const void *data) { - if (size == 0 || isImmutable()) - return; + if (size == 0 || isImmutable() || dataUsage == BUFFERDATAUSAGE_READBACK) + return false; size_t buffersize = getSize(); if (!Range(0, buffersize).contains(Range(offset, size))) - return; + return false; GLenum gldatausage = OpenGL::getGLBufferDataUsage(dataUsage); @@ -259,6 +284,8 @@ void Buffer::fill(size_t offset, size_t size, const void *data) { glBufferSubData(target, (GLintptr) offset, (GLsizeiptr) size, data); } + + return true; } void Buffer::copyTo(love::graphics::Buffer *dest, size_t sourceoffset, size_t destoffset, size_t size) diff --git a/src/modules/graphics/opengl/Buffer.h b/src/modules/graphics/opengl/Buffer.h index 849e92018..f7678eab7 100644 --- a/src/modules/graphics/opengl/Buffer.h +++ b/src/modules/graphics/opengl/Buffer.h @@ -52,12 +52,14 @@ public: void *map(MapType map, size_t offset, size_t size) override; void unmap(size_t usedoffset, size_t usedsize) override; - void fill(size_t offset, size_t size, const void *data) override; + bool fill(size_t offset, size_t size, const void *data) override; void copyTo(love::graphics::Buffer *dest, size_t sourceoffset, size_t destoffset, size_t size) override; ptrdiff_t getHandle() const override { return buffer; }; ptrdiff_t getTexelBufferHandle() const override { return texture; }; + BufferUsage getMapUsage() const { return mapUsage; } + private: bool load(const void *initialdata); diff --git a/src/modules/graphics/opengl/OpenGL.cpp b/src/modules/graphics/opengl/OpenGL.cpp index 164404e97..fd70ee3ba 100644 --- a/src/modules/graphics/opengl/OpenGL.cpp +++ b/src/modules/graphics/opengl/OpenGL.cpp @@ -843,7 +843,7 @@ GLenum OpenGL::getGLBufferDataUsage(BufferDataUsage usage) case BUFFERDATAUSAGE_STREAM: return GL_STREAM_DRAW; case BUFFERDATAUSAGE_DYNAMIC: return GL_DYNAMIC_DRAW; case BUFFERDATAUSAGE_STATIC: return GL_STATIC_DRAW; - case BUFFERDATAUSAGE_STAGING: + case BUFFERDATAUSAGE_READBACK: return (GLAD_VERSION_1_1 || GLAD_ES_VERSION_3_0) ? GL_STREAM_READ : GL_STREAM_DRAW; default: return 0; } diff --git a/src/modules/graphics/vertex.cpp b/src/modules/graphics/vertex.cpp index 6329cf30b..9896974f9 100644 --- a/src/modules/graphics/vertex.cpp +++ b/src/modules/graphics/vertex.cpp @@ -383,10 +383,10 @@ STRINGMAP_END(IndexDataType, INDEX_MAX_ENUM, indexType) STRINGMAP_BEGIN(BufferDataUsage, BUFFERDATAUSAGE_MAX_ENUM, bufferDataUsage) { - { "stream", BUFFERDATAUSAGE_STREAM }, - { "dynamic", BUFFERDATAUSAGE_DYNAMIC }, - { "static", BUFFERDATAUSAGE_STATIC }, - { "staging", BUFFERDATAUSAGE_STAGING }, + { "stream", BUFFERDATAUSAGE_STREAM }, + { "dynamic", BUFFERDATAUSAGE_DYNAMIC }, + { "static", BUFFERDATAUSAGE_STATIC }, + { "readback", BUFFERDATAUSAGE_READBACK }, } STRINGMAP_END(BufferDataUsage, BUFFERDATAUSAGE_MAX_ENUM, bufferDataUsage) diff --git a/src/modules/graphics/vertex.h b/src/modules/graphics/vertex.h index 73ed163fd..ea5b1493c 100644 --- a/src/modules/graphics/vertex.h +++ b/src/modules/graphics/vertex.h @@ -110,7 +110,7 @@ enum BufferDataUsage BUFFERDATAUSAGE_STREAM, BUFFERDATAUSAGE_DYNAMIC, BUFFERDATAUSAGE_STATIC, - BUFFERDATAUSAGE_STAGING, + BUFFERDATAUSAGE_READBACK, BUFFERDATAUSAGE_MAX_ENUM }; From fb450b1df4051ebb78f7e5f6f810407835a5e1ff Mon Sep 17 00:00:00 2001 From: Alex Szpakowski Date: Mon, 30 May 2022 21:46:55 -0300 Subject: [PATCH 14/18] Add internal code for temporary buffers. --- src/modules/graphics/Graphics.cpp | 106 ++++++++++++++++++++++- src/modules/graphics/Graphics.h | 27 +++++- src/modules/graphics/metal/Graphics.mm | 18 +--- src/modules/graphics/opengl/Graphics.cpp | 19 +--- 4 files changed, 134 insertions(+), 36 deletions(-) diff --git a/src/modules/graphics/Graphics.cpp b/src/modules/graphics/Graphics.cpp index a66f47196..aa2410d61 100644 --- a/src/modules/graphics/Graphics.cpp +++ b/src/modules/graphics/Graphics.cpp @@ -235,6 +235,8 @@ Graphics::~Graphics() for (int i = 0; i < (int) SHADERSTAGE_MAX_ENUM; i++) cachedShaderStages[i].clear(); + clearTemporaryResources(); + Shader::deinitialize(); } @@ -887,6 +889,10 @@ void Graphics::setRenderTargets(const RenderTargets &rts) realRTs.depthStencil.texture = getTemporaryTexture(dsformat, pixelw, pixelh, reqmsaa); realRTs.depthStencil.slice = 0; + // TODO: fix this to call release at the right time. + // This only works here because nothing else calls getTemporaryTexture. + releaseTemporaryTexture(realRTs.depthStencil.texture); + setRenderTargetsInternal(realRTs, pixelw, pixelh, hasSRGBtexture); } else @@ -989,12 +995,15 @@ Texture *Graphics::getTemporaryTexture(PixelFormat format, int w, int h, int sam for (TemporaryTexture &temp : temporaryTextures) { + if (temp.framesSinceUse < 0) + continue; + Texture *c = temp.texture; if (c->getPixelFormat() == format && c->getPixelWidth() == w && c->getPixelHeight() == h && c->getRequestedMSAA() == samples) { texture = c; - temp.framesSinceUse = 0; + temp.framesSinceUse = -1; break; } } @@ -1016,6 +1025,101 @@ Texture *Graphics::getTemporaryTexture(PixelFormat format, int w, int h, int sam return texture; } +void Graphics::releaseTemporaryTexture(Texture *texture) +{ + for (TemporaryTexture &temp : temporaryTextures) + { + if (temp.texture == texture) + { + temp.framesSinceUse = 0; + break; + } + } +} + +Buffer *Graphics::getTemporaryBuffer(size_t size, DataFormat format, uint32 usageflags, BufferDataUsage datausage) +{ + Buffer *buffer = nullptr; + + for (TemporaryBuffer &temp : temporaryBuffers) + { + if (temp.framesSinceUse < 0) + continue; + + Buffer *b = temp.buffer; + + if (temp.size == size && b->getDataMember(0).decl.format == format + && b->getUsageFlags() == usageflags && b->getDataUsage() == datausage) + { + buffer = b; + temp.framesSinceUse = -1; + break; + } + } + + if (buffer == nullptr) + { + Buffer::Settings settings(usageflags, datausage); + buffer = newBuffer(settings, format, nullptr, size, 0); + + temporaryBuffers.emplace_back(buffer, size); + } + + return buffer; +} + +void Graphics::releaseTemporaryBuffer(Buffer *buffer) +{ + for (TemporaryBuffer &temp : temporaryBuffers) + { + if (temp.buffer == buffer) + { + temp.framesSinceUse = 0; + break; + } + } +} + +void Graphics::updateTemporaryResources() +{ + for (int i = (int) temporaryTextures.size() - 1; i >= 0; i--) + { + auto &t = temporaryTextures[i]; + if (t.framesSinceUse >= MAX_TEMPORARY_RESOURCE_UNUSED_FRAMES) + { + t.texture->release(); + t = temporaryTextures.back(); + temporaryTextures.pop_back(); + } + else if (t.framesSinceUse >= 0) + t.framesSinceUse++; + } + + for (int i = (int) temporaryBuffers.size() - 1; i >= 0; i--) + { + auto &t = temporaryBuffers[i]; + if (t.framesSinceUse >= MAX_TEMPORARY_RESOURCE_UNUSED_FRAMES) + { + t.buffer->release(); + t = temporaryBuffers.back(); + temporaryBuffers.pop_back(); + } + else if (t.framesSinceUse >= 0) + t.framesSinceUse++; + } +} + +void Graphics::clearTemporaryResources() +{ + for (auto temp :temporaryBuffers) + temp.buffer->release(); + + for (auto temp : temporaryTextures) + temp.texture->release(); + + temporaryBuffers.clear(); + temporaryTextures.clear(); +} 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 61b6cbdbb..161b35d7e 100644 --- a/src/modules/graphics/Graphics.h +++ b/src/modules/graphics/Graphics.h @@ -856,6 +856,12 @@ public: static void flushBatchedDrawsGlobal(); + Texture *getTemporaryTexture(PixelFormat format, int w, int h, int samples); + void releaseTemporaryTexture(Texture *texture); + + Buffer *getTemporaryBuffer(size_t size, DataFormat format, uint32 usageflags, BufferDataUsage datausage); + void releaseTemporaryBuffer(Buffer *buffer); + void cleanupCachedShaderStage(ShaderStageType type, const std::string &cachekey); template @@ -954,6 +960,19 @@ protected: } }; + struct TemporaryBuffer + { + Buffer *buffer; + size_t size; + int framesSinceUse; + + TemporaryBuffer(Buffer *buf, size_t size) + : buffer(buf) + , size(size) + , framesSinceUse(-1) + {} + }; + struct TemporaryTexture { Texture *texture; @@ -961,7 +980,7 @@ protected: TemporaryTexture(Texture *tex) : texture(tex) - , framesSinceUse(0) + , framesSinceUse(-1) {} }; @@ -980,7 +999,8 @@ protected: void createQuadIndexBuffer(); void createFanIndexBuffer(); - Texture *getTemporaryTexture(PixelFormat format, int w, int h, int samples); + void updateTemporaryResources(); + void clearTemporaryResources(); void restoreState(const DisplayState &s); void restoreStateChecked(const DisplayState &s); @@ -1014,6 +1034,7 @@ protected: std::vector states; std::vector stackTypeStack; + std::vector temporaryBuffers; std::vector temporaryTextures; int renderTargetSwitchCount; @@ -1028,7 +1049,7 @@ protected: Deprecations deprecations; static const size_t MAX_USER_STACK_DEPTH = 128; - static const int MAX_TEMPORARY_TEXTURE_UNUSED_FRAMES = 16; + static const int MAX_TEMPORARY_RESOURCE_UNUSED_FRAMES = 16; private: diff --git a/src/modules/graphics/metal/Graphics.mm b/src/modules/graphics/metal/Graphics.mm index 2704118d0..90bc1edfd 100644 --- a/src/modules/graphics/metal/Graphics.mm +++ b/src/modules/graphics/metal/Graphics.mm @@ -501,10 +501,7 @@ void Graphics::unSetMode() submitCommandBuffer(SUBMIT_DONE); - for (auto temp : temporaryTextures) - temp.texture->release(); - - temporaryTextures.clear(); + clearTemporaryResources(); created = false; metalLayer = nil; @@ -1620,18 +1617,7 @@ void Graphics::present(void *screenshotCallbackData) renderTargetSwitchCount = 0; drawCallsBatched = 0; - // This assumes temporary canvases will only be used within a render pass. - for (int i = (int) temporaryTextures.size() - 1; i >= 0; i--) - { - if (temporaryTextures[i].framesSinceUse >= MAX_TEMPORARY_TEXTURE_UNUSED_FRAMES) - { - temporaryTextures[i].texture->release(); - temporaryTextures[i] = temporaryTextures.back(); - temporaryTextures.pop_back(); - } - else - temporaryTextures[i].framesSinceUse++; - } + updateTemporaryResources(); }} int Graphics::getRequestedBackbufferMSAA() const diff --git a/src/modules/graphics/opengl/Graphics.cpp b/src/modules/graphics/opengl/Graphics.cpp index df1b911fc..92e48634e 100644 --- a/src/modules/graphics/opengl/Graphics.cpp +++ b/src/modules/graphics/opengl/Graphics.cpp @@ -462,14 +462,12 @@ void Graphics::unSetMode() // mode change. Volatile::unloadAll(); + clearTemporaryResources(); + for (const auto &pair : framebufferObjects) gl.deleteFramebuffer(pair.second); - for (auto temp : temporaryTextures) - temp.texture->release(); - framebufferObjects.clear(); - temporaryTextures.clear(); if (mainVAO != 0) { @@ -1335,18 +1333,7 @@ void Graphics::present(void *screenshotCallbackData) renderTargetSwitchCount = 0; drawCallsBatched = 0; - // This assumes temporary textures will only be used within a render pass. - for (int i = (int) temporaryTextures.size() - 1; i >= 0; i--) - { - if (temporaryTextures[i].framesSinceUse >= MAX_TEMPORARY_TEXTURE_UNUSED_FRAMES) - { - temporaryTextures[i].texture->release(); - temporaryTextures[i] = temporaryTextures.back(); - temporaryTextures.pop_back(); - } - else - temporaryTextures[i].framesSinceUse++; - } + updateTemporaryResources(); } int Graphics::getRequestedBackbufferMSAA() const From 3a5c3df02f6091297481bfca34c8bf793f8d3cef Mon Sep 17 00:00:00 2001 From: Alex Szpakowski Date: Mon, 30 May 2022 21:53:55 -0300 Subject: [PATCH 15/18] 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 } }; From 4a1d01c68c1578194156de124f608f538a47e0f5 Mon Sep 17 00:00:00 2001 From: Alex Szpakowski Date: Mon, 30 May 2022 22:14:02 -0300 Subject: [PATCH 16/18] Fix cmake builds --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 959e32d91..2b23406a1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,7 +34,7 @@ set(CMAKE_MODULE_PATH "${love_SOURCE_DIR}/extra/cmake" ${CMAKE_MODULE_PATH}) # Needed for shared libs on Linux. (-fPIC). set(CMAKE_POSITION_INDEPENDENT_CODE TRUE) -set (CMAKE_CXX_STANDARD 11) +set (CMAKE_CXX_STANDARD 17) if(APPLE) message(WARNING "CMake is not an officially supported build system for love on Apple platforms.") From 07f087c139dcbec78863df954f44cbe088ac0b9b Mon Sep 17 00:00:00 2001 From: Alex Szpakowski Date: Thu, 9 Jun 2022 19:57:31 -0300 Subject: [PATCH 17/18] Fix newFileData and newThread outputting deprecation warnings. Fixes #1811. --- src/modules/filesystem/wrap_Filesystem.cpp | 56 ++++++++++------------ 1 file changed, 26 insertions(+), 30 deletions(-) diff --git a/src/modules/filesystem/wrap_Filesystem.cpp b/src/modules/filesystem/wrap_Filesystem.cpp index 4b53a86c7..d2f004b27 100644 --- a/src/modules/filesystem/wrap_Filesystem.cpp +++ b/src/modules/filesystem/wrap_Filesystem.cpp @@ -305,7 +305,7 @@ File *luax_getfile(lua_State *L, int idx) return file; } -FileData *luax_getfiledata(lua_State *L, int idx) +FileData *luax_getfiledata(lua_State *L, int idx, bool ioerror) { FileData *data = nullptr; File *file = nullptr; @@ -325,18 +325,33 @@ FileData *luax_getfiledata(lua_State *L, int idx) luaL_argerror(L, idx, "filename, File, or FileData expected"); return nullptr; // Never reached. } - - if (file) + else if (file && !data) { - luax_catchexcept(L, - [&]() { data = file->read(); }, - [&](bool) { file->release(); } - ); + try + { + data = file->read(); + } + catch (love::Exception &e) + { + file->release(); + if (ioerror) + luax_ioError(L, "%s", e.what()); + else + luaL_error(L, "%s", e.what()); + return nullptr; // Never reached. + } + + file->release(); } return data; } +FileData *luax_getfiledata(lua_State *L, int idx) +{ + return luax_getfiledata(L, idx, false); +} + Data *luax_getdata(lua_State *L, int idx) { Data *data = nullptr; @@ -389,29 +404,10 @@ int w_newFileData(lua_State *L) // Single argument: treat as filepath or File. if (lua_gettop(L) == 1) { - // We don't use luax_getfiledata because we want to use an ioError. - if (lua_isstring(L, 1)) - luax_convobj(L, 1, "filesystem", "newFile"); - - // Get FileData from the File. - if (luax_istype(L, 1, File::type)) - { - File *file = luax_checkfile(L, 1); - - StrongRef data; - try - { - data.set(file->read(), Acquire::NORETAIN); - } - catch (love::Exception &e) - { - return luax_ioError(L, "%s", e.what()); - } - luax_pushtype(L, data); - return 1; - } - else - return luaL_argerror(L, 1, "filename or File expected"); + FileData *data = luax_getfiledata(L, 1, true); + luax_pushtype(L, data); + data->release(); + return 1; } size_t length = 0; From dd7e20fe18b20df7b96380b689fe3107647fd586 Mon Sep 17 00:00:00 2001 From: Alex Szpakowski Date: Thu, 9 Jun 2022 19:57:53 -0300 Subject: [PATCH 18/18] Xcode project file maintenance --- platform/xcode/liblove.xcodeproj/project.pbxproj | 10 +++++----- platform/xcode/love.xcodeproj/project.pbxproj | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/platform/xcode/liblove.xcodeproj/project.pbxproj b/platform/xcode/liblove.xcodeproj/project.pbxproj index c1aa7a6d8..b1a531cdf 100644 --- a/platform/xcode/liblove.xcodeproj/project.pbxproj +++ b/platform/xcode/liblove.xcodeproj/project.pbxproj @@ -823,11 +823,11 @@ FA6A2B7A1F60B8250074C308 /* wrap_ByteData.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA6A2B781F60B8250074C308 /* wrap_ByteData.cpp */; }; FA6A2B7B1F60B8250074C308 /* wrap_ByteData.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA6A2B781F60B8250074C308 /* wrap_ByteData.cpp */; }; FA6BDE5C1F31725300786805 /* Color.h in Headers */ = {isa = PBXBuildFile; fileRef = FA6BDE5B1F31725300786805 /* Color.h */; }; + FA6BDF89280B62A000240F2A /* GraphicsReadback.mm in Sources */ = {isa = PBXBuildFile; fileRef = FA6BDF88280B62A000240F2A /* GraphicsReadback.mm */; }; + FA6BDF8A280B62A000240F2A /* GraphicsReadback.mm in Sources */ = {isa = PBXBuildFile; fileRef = FA6BDF88280B62A000240F2A /* GraphicsReadback.mm */; }; 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 */; }; @@ -1917,10 +1917,10 @@ FA6A2B771F60B8250074C308 /* wrap_ByteData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wrap_ByteData.h; sourceTree = ""; }; FA6A2B781F60B8250074C308 /* wrap_ByteData.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = wrap_ByteData.cpp; sourceTree = ""; }; 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 = ""; }; + 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 = ""; }; 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 = ""; }; @@ -4496,7 +4496,7 @@ 08FB7793FE84155DC02AAC07 /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1310; + LastUpgradeCheck = 1340; TargetAttributes = { FA0B78DC1A958B90000E1D17 = { CreatedOnToolsVersion = 6.1.1; diff --git a/platform/xcode/love.xcodeproj/project.pbxproj b/platform/xcode/love.xcodeproj/project.pbxproj index 8689ff40e..418f8d202 100644 --- a/platform/xcode/love.xcodeproj/project.pbxproj +++ b/platform/xcode/love.xcodeproj/project.pbxproj @@ -328,7 +328,7 @@ 29B97313FDCFA39411CA2CEA /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1310; + LastUpgradeCheck = 1340; TargetAttributes = { FA0B7F051A95AAF3000E1D17 = { CreatedOnToolsVersion = 6.1.1;