Merge remote-tracking branch 'origin/12.0-development' into vulkan

This commit is contained in:
niki
2022-06-11 01:22:29 +02:00
55 changed files with 2101 additions and 541 deletions
+5
View File
@@ -78,6 +78,11 @@ bool Audio::setMixWithSystem(bool mix)
#endif
}
void Audio::setPlaybackDevice(const char */*name*/)
{
throw love::Exception("Re-setting output device is not supported.");
}
StringMap<Audio::DistanceModel, Audio::DISTANCE_MAX_ENUM>::Entry Audio::distanceModelEntries[] =
{
{"none", Audio::DISTANCE_NONE},
+15
View File
@@ -297,6 +297,21 @@ public:
virtual void pauseContext() = 0;
virtual void resumeContext() = 0;
/**
* Get current playback device name.
*/
virtual std::string getPlaybackDevice() = 0;
/**
* Retrieve list of available playback devices.
*/
virtual void getPlaybackDevices(std::vector<std::string> &list) = 0;
/**
* Set the current playback device to specified device name.
*/
virtual void setPlaybackDevice(const char *name);
private:
static StringMap<DistanceModel, DISTANCE_MAX_ENUM>::Entry distanceModelEntries[];
+9
View File
@@ -211,6 +211,15 @@ void Audio::resumeContext()
{
}
std::string Audio::getPlaybackDevice()
{
return "";
}
void Audio::getPlaybackDevices(std::vector<std::string> &/*list*/)
{
}
} // null
} // audio
+3
View File
@@ -89,6 +89,9 @@ public:
void pauseContext();
void resumeContext();
std::string getPlaybackDevice();
void getPlaybackDevices(std::vector<std::string> &list);
private:
float volume;
DistanceModel distanceModel;
+65 -5
View File
@@ -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_TRUE
? ALC_ALL_DEVICES_SPECIFIER
: ALC_DEVICE_SPECIFIER;
return alcGetString(device, deviceEnum);
}
Audio::Audio()
: device(nullptr)
, context(nullptr)
@@ -100,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())
@@ -122,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.");
@@ -165,7 +179,7 @@ Audio::Audio()
try
{
pool = new Pool();
pool = new Pool(device);
}
catch (love::Exception &)
{
@@ -314,6 +328,52 @@ void Audio::resumeContext()
alcMakeContextCurrent(context);
}
std::string Audio::getPlaybackDevice()
{
const char *dev = getDeviceSpecifier(device);
if (dev == nullptr)
throw Exception("Failed to get current device: %s", alcGetString(device, alcGetError(device)));
return dev;
}
void Audio::getPlaybackDevices(std::vector<std::string> &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::setPlaybackDevice(const char* name)
{
#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::setPlaybackDevice(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);
+5
View File
@@ -127,6 +127,10 @@ public:
bool getEffectID(const char *name, ALuint &id);
std::string getPlaybackDevice();
void getPlaybackDevices(std::vector<std::string> &list);
void setPlaybackDevice(const char *name);
private:
void initializeEFX();
// The OpenAL device.
@@ -137,6 +141,7 @@ private:
// The OpenAL context.
ALCcontext *context;
std::vector<ALCint> attribs;
// The OpenAL effects
struct EffectMapStorage
+50 -2
View File
@@ -20,6 +20,7 @@
#include "Pool.h"
#include "event/Event.h"
#include "Source.h"
namespace love
@@ -29,8 +30,20 @@ namespace audio
namespace openal
{
Pool::Pool()
: sources()
static Variant::SharedTable *putSourcesAsSharedTable(std::vector<audio::Source *> &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()
, disconnectNotified(false)
, totalSources(0)
{
// Clear errors.
@@ -101,8 +114,43 @@ 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<event::Event>(Module::M_EVENT);
if (eventModule)
{
ALCint connected;
alcGetIntegerv(device, ALC_CONNECTED, 1, &connected);
if (connected)
disconnectNotified = false;
else if (!disconnectNotified)
{
// Get all sources in this Pool then stop it
// since they're all internally stopped.
std::vector<audio::Source *> sources = getPlayingSources();
Source::stop(sources);
std::vector<Variant> vargs;
vargs.emplace_back(putSourcesAsSharedTable(sources));
StrongRef<event::Message> msg(new event::Message("audiodisconnected", vargs), Acquire::NORETAIN);
eventModule->push(msg);
disconnectNotified = true;
}
}
}
std::vector<Source *> torelease;
for (const auto &i : playing)
+7 -1
View File
@@ -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;
+73 -21
View File
@@ -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))
@@ -543,6 +546,52 @@ int w_setMixWithSystem(lua_State *L)
return 1;
}
int w_getPlaybackDevice(lua_State* L)
{
std::string device;
luax_catchexcept(L, [&]() { device = instance()->getPlaybackDevice(); });
luax_pushstring(L, device);
return 1;
}
int w_getPlaybackDevices(lua_State* L)
{
std::vector<std::string> list;
luax_catchexcept(L, [&]() { instance()->getPlaybackDevices(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;
}
int w_setPlaybackDevice(lua_State* L)
{
const char *device = luaL_optstring(L, 1, nullptr);
try
{
instance()->setPlaybackDevice(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[] =
{
@@ -574,6 +623,9 @@ static const luaL_Reg functions[] =
{ "getMaxSourceEffects", w_getMaxSourceEffects },
{ "isEffectsSupported", w_isEffectsSupported },
{ "setMixWithSystem", w_setMixWithSystem },
{ "getPlaybackDevice", w_getPlaybackDevice },
{ "getPlaybackDevices", w_getPlaybackDevices },
{ "setPlaybackDevice", w_setPlaybackDevice },
{ 0, 0 }
};
+20 -1
View File
@@ -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 <sys/types.h>
#include <sys/stat.h>
@@ -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);
+10 -26
View File
@@ -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;
+26 -30
View File
@@ -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<FileData> 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;
+3 -2
View File
@@ -36,6 +36,7 @@ Buffer::Buffer(Graphics *gfx, const Settings &settings, const std::vector<DataDe
, usageFlags(settings.usageFlags)
, dataUsage(settings.dataUsage)
, mapped(false)
, mappedType(MAP_WRITE_INVALIDATE)
, immutable(false)
{
if (size == 0 && arraylength == 0)
@@ -61,8 +62,8 @@ Buffer::Buffer(Graphics *gfx, const Settings &settings, const std::vector<DataDe
if (storagebuffer && dataUsage == BUFFERDATAUSAGE_STREAM)
throw love::Exception("Buffers created with 'stream' data usage cannot be used as a shader storage buffer.");
if (dataUsage == BUFFERDATAUSAGE_STAGING && (indexbuffer || vertexbuffer || texelbuffer || storagebuffer))
throw love::Exception("Buffers created with 'staging' data usage cannot be index, vertex, texel, or shaderstorage buffer types.");
if (dataUsage == BUFFERDATAUSAGE_READBACK && (indexbuffer || vertexbuffer || texelbuffer || storagebuffer))
throw love::Exception("Buffers created with 'readback' data usage cannot be index, vertex, texel, or shaderstorage buffer types.");
size_t offset = 0;
size_t stride = 0;
+5 -4
View File
@@ -53,6 +53,7 @@ public:
enum MapType
{
MAP_WRITE_INVALIDATE,
MAP_READ_ONLY,
};
struct DataDeclaration
@@ -128,7 +129,7 @@ public:
/**
* Fill a portion of the buffer with data.
*/
virtual void fill(size_t offset, size_t size, const void *data) = 0;
virtual bool fill(size_t offset, size_t size, const void *data) = 0;
/**
* Copy a portion of this Buffer's data to another buffer, using the GPU.
@@ -147,10 +148,10 @@ public:
{
public:
Mapper(Buffer &buffer)
Mapper(Buffer &buffer, MapType maptype = MAP_WRITE_INVALIDATE)
: buffer(buffer)
{
data = buffer.map(MAP_WRITE_INVALIDATE, 0, buffer.getSize());
data = buffer.map(maptype, 0, buffer.getSize());
}
~Mapper()
@@ -179,7 +180,7 @@ protected:
BufferDataUsage dataUsage;
bool mapped;
MapType mappedType;
bool immutable;
}; // Buffer
+166 -1
View File
@@ -240,6 +240,9 @@ Graphics::~Graphics()
for (int i = 0; i < (int) SHADERSTAGE_MAX_ENUM; i++)
cachedShaderStages[i].clear();
pendingReadbacks.clear();
clearTemporaryResources();
Shader::deinitialize();
}
@@ -438,6 +441,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<GraphicsReadback> 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<GraphicsReadback> 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);
@@ -893,6 +936,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
@@ -995,12 +1042,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;
}
}
@@ -1022,6 +1072,115 @@ 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::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;
@@ -1187,6 +1346,9 @@ void Graphics::copyBuffer(Buffer *source, Buffer *dest, size_t sourceoffset, siz
if (dest->getDataUsage() == 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.");
@@ -1296,6 +1458,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))
+37 -3
View File
@@ -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"
@@ -461,6 +462,12 @@ public:
Text *newText(Font *font, const std::vector<Font::ColoredString> &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<std::string> &stages, const Shader::CompileOptions &options, std::string &err);
/**
@@ -857,6 +864,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 <typename T>
@@ -955,6 +968,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;
@@ -962,7 +988,7 @@ protected:
TemporaryTexture(Texture *tex)
: texture(tex)
, framesSinceUse(0)
, framesSinceUse(-1)
{}
};
@@ -971,6 +997,9 @@ protected:
virtual Shader *newShaderInternal(StrongRef<ShaderStage> 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;
@@ -981,7 +1010,10 @@ protected:
void createQuadIndexBuffer();
void createFanIndexBuffer();
Texture *getTemporaryTexture(PixelFormat format, int w, int h, int samples);
void updateTemporaryResources();
void clearTemporaryResources();
void updatePendingReadbacks();
void restoreState(const DisplayState &s);
void restoreStateChecked(const DisplayState &s);
@@ -1004,6 +1036,7 @@ protected:
StrongRef<love::graphics::Font> defaultFont;
std::vector<ScreenshotInfo> pendingScreenshotCallbacks;
std::vector<StrongRef<GraphicsReadback>> pendingReadbacks;
BatchedDrawState batchedDrawState;
@@ -1015,6 +1048,7 @@ protected:
std::vector<DisplayState> states;
std::vector<StackType> stackTypeStack;
std::vector<TemporaryBuffer> temporaryBuffers;
std::vector<TemporaryTexture> temporaryTextures;
int renderTargetSwitchCount;
@@ -1029,7 +1063,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:
+229
View File
@@ -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<image::Image>(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
+112
View File
@@ -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<love::data::ByteData> bufferData;
size_t bufferDataOffset = 0;
StrongRef<love::image::ImageData> imageData;
Rect rect = {};
PixelFormat textureFormat = PIXELFORMAT_UNKNOWN;
int imageDataX = 0;
int imageDataY = 0;
}; // GraphicsReadback
} // graphics
} // love
-41
View File
@@ -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<Graphics>(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;
-3
View File
@@ -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;
+1 -1
View File
@@ -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; }
+38 -24
View File
@@ -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<MTLDevice> 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)
+4
View File
@@ -199,6 +199,10 @@ private:
love::graphics::ShaderStage *newShaderStageInternal(ShaderStageType stage, const std::string &cachekey, const std::string &source, bool gles) override;
love::graphics::Shader *newShaderInternal(StrongRef<love::graphics::ShaderStage> stages[SHADERSTAGE_MAX_ENUM]) override;
love::graphics::StreamBuffer *newStreamBuffer(BufferUsage usage, size_t size) override;
love::graphics::GraphicsReadback *newReadbackInternal(ReadbackMethod method, love::graphics::Buffer *buffer, size_t offset, size_t size, data::ByteData *dest, size_t destoffset) override;
love::graphics::GraphicsReadback *newReadbackInternal(ReadbackMethod method, love::graphics::Texture *texture, int slice, int mipmap, const Rect &rect, image::ImageData *dest, int destx, int desty) override;
void setRenderTargetsInternal(const RenderTargets &rts, int pixelw, int pixelh, bool hasSRGBcanvas) override;
void initCapabilities() override;
void getAPIStats(int &shaderswitches) const override;
+14 -16
View File
@@ -22,6 +22,7 @@
#include "StreamBuffer.h"
#include "Buffer.h"
#include "Texture.h"
#include "GraphicsReadback.h"
#include "Shader.h"
#include "ShaderStage.h"
#include "window/Window.h"
@@ -418,6 +419,16 @@ love::graphics::Buffer *Graphics::newBuffer(const Buffer::Settings &settings, co
return new Buffer(this, device, settings, format, data, size, arraylength);
}
love::graphics::GraphicsReadback *Graphics::newReadbackInternal(ReadbackMethod method, love::graphics::Buffer *buffer, size_t offset, size_t size, data::ByteData *dest, size_t destoffset)
{
return new GraphicsReadback(this, method, buffer, offset, size, dest, destoffset);
}
love::graphics::GraphicsReadback *Graphics::newReadbackInternal(ReadbackMethod method, love::graphics::Texture *texture, int slice, int mipmap, const Rect &rect, image::ImageData *dest, int destx, int desty)
{
return new GraphicsReadback(this, method, texture, slice, mipmap, rect, dest, destx, desty);
}
Matrix4 Graphics::computeDeviceProjection(const Matrix4 &projection, bool /*rendertotexture*/) const
{
uint32 flags = DEVICE_PROJECTION_FLIP_Y;
@@ -501,10 +512,7 @@ void Graphics::unSetMode()
submitCommandBuffer(SUBMIT_DONE);
for (auto temp : temporaryTextures)
temp.texture->release();
temporaryTextures.clear();
clearTemporaryResources();
created = false;
metalLayer = nil;
@@ -1620,18 +1628,8 @@ 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++;
}
updatePendingReadbacks();
updateTemporaryResources();
}}
int Graphics::getRequestedBackbufferMSAA() const
@@ -0,0 +1,54 @@
/**
* Copyright (c) 2006-2022 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
**/
#pragma once
// LOVE
#include "graphics/GraphicsReadback.h"
#include "common/math.h"
#include <atomic>
#import <Metal/MTLCommandBuffer.h>
namespace love::graphics::metal
{
class GraphicsReadback final : public love::graphics::GraphicsReadback
{
public:
GraphicsReadback(love::graphics::Graphics *gfx, ReadbackMethod method, love::graphics::Buffer *buffer, size_t offset, size_t size, data::ByteData *dest, size_t destoffset);
GraphicsReadback(love::graphics::Graphics *gfx, ReadbackMethod method, love::graphics::Texture *texture, int slice, int mipmap, const Rect &rect, image::ImageData *dest, int destx, int desty);
virtual ~GraphicsReadback();
void wait() override;
void update() override;
private:
id<MTLCommandBuffer> cmd;
std::atomic_bool done;
StrongRef<love::graphics::Buffer> stagingBuffer;
}; // GraphicsReadback
} // love::graphics::metal
@@ -0,0 +1,143 @@
/**
* Copyright (c) 2006-2022 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
**/
#include "GraphicsReadback.h"
#include "Buffer.h"
#include "Texture.h"
#include "Graphics.h"
#include "data/ByteData.h"
namespace love::graphics::metal
{
GraphicsReadback::GraphicsReadback(love::graphics::Graphics *gfx, ReadbackMethod method, love::graphics::Buffer *buffer, size_t offset, size_t size, data::ByteData *dest, size_t destoffset)
: love::graphics::GraphicsReadback(gfx, method, buffer, offset, size, dest, destoffset)
, done(false)
{ @autoreleasepool {
auto mgfx = (Graphics *) gfx;
// Immediate readback of readback-type buffers doesn't need a staging buffer.
if (method != READBACK_IMMEDIATE || buffer->getDataUsage() != BUFFERDATAUSAGE_READBACK)
{
stagingBuffer = gfx->getTemporaryBuffer(size, DATAFORMAT_FLOAT, 0, BUFFERDATAUSAGE_READBACK);
gfx->copyBuffer(buffer, stagingBuffer, offset, 0, size);
}
// use instead of get, in case this was the first command in the frame.
cmd = mgfx->useCommandBuffer();
auto pthis = this;
pthis->retain();
[cmd addCompletedHandler:^(id<MTLCommandBuffer> _Nonnull)
{
pthis->done = true;
pthis->release();
}];
if (method == READBACK_IMMEDIATE)
{
wait();
if (stagingBuffer.get())
{
status = readbackBuffer(stagingBuffer, 0, size);
gfx->releaseTemporaryBuffer(stagingBuffer);
}
else
{
status = readbackBuffer(buffer, offset, size);
}
}
}}
GraphicsReadback::GraphicsReadback(love::graphics::Graphics *gfx, ReadbackMethod method, love::graphics::Texture *texture, int slice, int mipmap, const Rect &rect, image::ImageData *dest, int destx, int desty)
: love::graphics::GraphicsReadback(gfx, method, texture, slice, mipmap, rect, dest, destx, desty)
, done(false)
{ @autoreleasepool {
auto mgfx = (Graphics *) gfx;
size_t size = getPixelFormatSliceSize(textureFormat, rect.w, rect.h);
stagingBuffer = gfx->getTemporaryBuffer(size, DATAFORMAT_FLOAT, 0, BUFFERDATAUSAGE_READBACK);
gfx->copyTextureToBuffer(texture, stagingBuffer, slice, mipmap, rect, 0, 0);
cmd = mgfx->getCommandBuffer();
auto pthis = this;
pthis->retain();
[cmd addCompletedHandler:^(id<MTLCommandBuffer> _Nonnull)
{
pthis->done = true;
pthis->release();
}];
if (method == READBACK_IMMEDIATE)
{
wait();
status = readbackBuffer(stagingBuffer, 0, size);
gfx->releaseTemporaryBuffer(stagingBuffer);
}
}}
GraphicsReadback::~GraphicsReadback()
{ @autoreleasepool {
cmd = nil;
}}
void GraphicsReadback::wait()
{ @autoreleasepool {
if (status != STATUS_WAITING || cmd == nil)
return;
if (cmd.status == MTLCommandBufferStatusNotEnqueued)
{
auto gfx = Graphics::getInstance();
gfx->submitCommandBuffer(Graphics::SUBMIT_STORE);
}
[cmd waitUntilCompleted];
cmd = nil;
update();
}}
void GraphicsReadback::update()
{
if (status != STATUS_WAITING)
return;
if (done)
{
if (stagingBuffer.get())
status = readbackBuffer(stagingBuffer, 0, stagingBuffer->getSize());
else
status = STATUS_ERROR;
if (stagingBuffer.get())
{
auto gfx = Module::getInstance<love::graphics::Graphics>(Module::M_GRAPHICS);
if (gfx != nullptr)
gfx->releaseTemporaryBuffer(stagingBuffer);
stagingBuffer.set(nullptr);
}
}
}
} // love::graphics::metal
-1
View File
@@ -57,7 +57,6 @@ private:
void uploadByteData(PixelFormat pixelformat, const void *data, size_t size, int level, int slice, const Rect &r) override;
void generateMipmapsInternal() override;
void readbackImageData(love::image::ImageData *imagedata, int slice, int mipmap, const Rect &rect) override;
id<MTLTexture> texture;
id<MTLTexture> msaaTexture;
-46
View File
@@ -273,52 +273,6 @@ void Texture::generateMipmapsInternal()
[encoder generateMipmapsForTexture:texture];
}}
void Texture::readbackImageData(love::image::ImageData *imagedata, int slice, int mipmap, const Rect &rect)
{ @autoreleasepool {
auto gfx = Graphics::getInstance();
id<MTLBlitCommandEncoder> encoder = gfx->useBlitEncoder();
size_t rowSize = 0;
if (isCompressed())
rowSize = getPixelFormatCompressedBlockRowSize(format, rect.w);
else
rowSize = getPixelFormatUncompressedRowSize(format, rect.w);
// TODO: Verify this is correct for compressed formats at small sizes.
// TODO: make sure this is consistent with the imagedata byte size?
size_t sliceSize = getPixelFormatSliceSize(format, rect.w, rect.h);
int z = texType == TEXTURE_VOLUME ? slice : 0;
id<MTLBuffer> buffer = [gfx->device newBufferWithLength:sliceSize
options:MTLResourceStorageModeShared];
MTLBlitOption options = MTLBlitOptionNone;
if (isPixelFormatDepthStencil(format))
options = MTLBlitOptionDepthFromDepthStencil;
[encoder copyFromTexture:texture
sourceSlice:texType == TEXTURE_VOLUME ? 0 : slice
sourceLevel:mipmap
sourceOrigin:MTLOriginMake(rect.x, rect.y, z)
sourceSize:MTLSizeMake(rect.w, rect.h, 1)
toBuffer:buffer
destinationOffset:0
destinationBytesPerRow:rowSize
destinationBytesPerImage:sliceSize
options:options];
id<MTLCommandBuffer> cmd = gfx->getCommandBuffer();
gfx->submitBlitEncoder();
gfx->submitCommandBuffer(Graphics::SUBMIT_STORE);
[cmd waitUntilCompleted];
memcpy(imagedata->getData(), buffer.contents, imagedata->getSize());
}}
void Texture::copyFromBuffer(love::graphics::Buffer *source, size_t sourceoffset, int sourcewidth, size_t size, int slice, int mipmap, const Rect &rect)
{ @autoreleasepool {
id<MTLBlitCommandEncoder> encoder = Graphics::getInstance()->useBlitEncoder();
+35 -8
View File
@@ -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)
+3 -1
View File
@@ -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);
+16
View File
@@ -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)
+1
View File
@@ -42,6 +42,7 @@ public:
~FenceSync();
bool fence();
bool isComplete() const;
bool cpuWait();
void cleanup();
+15 -16
View File
@@ -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;
@@ -462,14 +473,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 +1344,8 @@ 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++;
}
updatePendingReadbacks();
updateTemporaryResources();
}
int Graphics::getRequestedBackbufferMSAA() const
+4
View File
@@ -141,6 +141,10 @@ private:
love::graphics::ShaderStage *newShaderStageInternal(ShaderStageType stage, const std::string &cachekey, const std::string &source, bool gles) override;
love::graphics::Shader *newShaderInternal(StrongRef<love::graphics::ShaderStage> stages[SHADERSTAGE_MAX_ENUM]) override;
love::graphics::StreamBuffer *newStreamBuffer(BufferUsage type, size_t size) override;
love::graphics::GraphicsReadback *newReadbackInternal(ReadbackMethod method, love::graphics::Buffer *buffer, size_t offset, size_t size, data::ByteData *dest, size_t destoffset) override;
love::graphics::GraphicsReadback *newReadbackInternal(ReadbackMethod method, love::graphics::Texture *texture, int slice, int mipmap, const Rect &rect, image::ImageData *dest, int destx, int desty) override;
void setRenderTargetsInternal(const RenderTargets &rts, int pixelw, int pixelh, bool hasSRGBtexture) override;
void initCapabilities() override;
void getAPIStats(int &shaderswitches) const override;
@@ -0,0 +1,126 @@
/**
* Copyright (c) 2006-2022 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
**/
#include "GraphicsReadback.h"
#include "Buffer.h"
#include "Texture.h"
#include "graphics/Graphics.h"
#include "data/ByteData.h"
namespace love
{
namespace graphics
{
namespace opengl
{
GraphicsReadback::GraphicsReadback(love::graphics::Graphics *gfx, ReadbackMethod method, love::graphics::Buffer *buffer, size_t offset, size_t size, data::ByteData *dest, size_t destoffset)
: love::graphics::GraphicsReadback(gfx, method, buffer, offset, size, dest, destoffset)
{
// Immediate readback of readback-type buffers doesn't need a staging buffer.
if (method != READBACK_IMMEDIATE || buffer->getDataUsage() != BUFFERDATAUSAGE_READBACK)
{
stagingBuffer = gfx->getTemporaryBuffer(size, DATAFORMAT_FLOAT, 0, BUFFERDATAUSAGE_READBACK);
gfx->copyBuffer(buffer, stagingBuffer, offset, 0, size);
}
if (method == READBACK_IMMEDIATE)
{
if (stagingBuffer.get())
{
status = readbackBuffer(stagingBuffer, 0, size);
gfx->releaseTemporaryBuffer(stagingBuffer);
}
else
{
status = readbackBuffer(buffer, offset, size);
}
}
else
{
sync.fence();
}
}
GraphicsReadback::GraphicsReadback(love::graphics::Graphics *gfx, ReadbackMethod method, love::graphics::Texture *texture, int slice, int mipmap, const Rect &rect, image::ImageData *dest, int destx, int desty)
: love::graphics::GraphicsReadback(gfx, method, texture, slice, mipmap, rect, dest, destx, desty)
{
size_t size = getPixelFormatSliceSize(textureFormat, rect.w, rect.h);
if (method == READBACK_IMMEDIATE)
{
void *dest = prepareReadbackDest(size);
love::thread::Lock lock(imageData->getMutex());
// Direct readback without copying avoids the need for a staging buffer,
// and lowers the system requirements of immediate RT readback.
Texture *t = (Texture *) texture;
t->readbackInternal(slice, mipmap, rect, imageData->getWidth(), size, dest);
status = STATUS_COMPLETE;
}
else
{
stagingBuffer = gfx->getTemporaryBuffer(size, DATAFORMAT_FLOAT, 0, BUFFERDATAUSAGE_READBACK);
gfx->copyTextureToBuffer(texture, stagingBuffer, slice, mipmap, rect, 0, 0);
sync.fence();
}
}
GraphicsReadback::~GraphicsReadback()
{
}
void GraphicsReadback::wait()
{
if (status != STATUS_WAITING)
return;
sync.cpuWait();
update();
}
void GraphicsReadback::update()
{
if (status != STATUS_WAITING)
return;
if (sync.isComplete())
{
if (stagingBuffer.get())
status = readbackBuffer(stagingBuffer, 0, stagingBuffer->getSize());
else
status = STATUS_ERROR;
if (stagingBuffer.get())
{
auto gfx = Module::getInstance<love::graphics::Graphics>(Module::M_GRAPHICS);
if (gfx != nullptr)
gfx->releaseTemporaryBuffer(stagingBuffer);
stagingBuffer.set(nullptr);
}
}
}
} // opengl
} // graphics
} // love
@@ -0,0 +1,56 @@
/**
* Copyright (c) 2006-2022 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
**/
#pragma once
// LOVE
#include "graphics/GraphicsReadback.h"
#include "FenceSync.h"
#include "common/math.h"
namespace love
{
namespace graphics
{
namespace opengl
{
class GraphicsReadback final : public love::graphics::GraphicsReadback
{
public:
GraphicsReadback(love::graphics::Graphics *gfx, ReadbackMethod method, love::graphics::Buffer *buffer, size_t offset, size_t size, data::ByteData *dest, size_t destoffset);
GraphicsReadback(love::graphics::Graphics *gfx, ReadbackMethod method, love::graphics::Texture *texture, int slice, int mipmap, const Rect &rect, image::ImageData *dest, int destx, int desty);
virtual ~GraphicsReadback();
void wait() override;
void update() override;
private:
FenceSync sync;
StrongRef<love::graphics::Buffer> stagingBuffer;
}; // GraphicsReadback
} // opengl
} // graphics
} // love
+1 -1
View File
@@ -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;
}
+35 -53
View File
@@ -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);
}
+2 -2
View File
@@ -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();
@@ -65,8 +67,6 @@ private:
void generateMipmapsInternal() override;
void readbackImageData(love::image::ImageData *imagedata, int slice, int mipmap, const Rect &rect) override;
Slices slices;
GLuint fbo;
+4 -4
View File
@@ -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)
+1 -1
View File
@@ -111,7 +111,7 @@ enum BufferDataUsage
BUFFERDATAUSAGE_STREAM,
BUFFERDATAUSAGE_DYNAMIC,
BUFFERDATAUSAGE_STATIC,
BUFFERDATAUSAGE_STAGING,
BUFFERDATAUSAGE_READBACK,
BUFFERDATAUSAGE_MAX_ENUM
};
+126
View File
@@ -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<data::ByteData>(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<data::ByteData>(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<image::ImageData>(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<image::ImageData>(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,
+1
View File
@@ -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
@@ -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<GraphicsReadback>(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
@@ -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
+12 -6
View File
@@ -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<love::image::Image>(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<Graphics>(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 }
};
+2
View File
@@ -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;
+5
View File
@@ -129,6 +129,11 @@ function love.createhandlers()
localechanged = function ()
if love.localechanged then return love.localechanged() end
end,
audiodisconnected = function (sources)
if not love.audiodisconnected or not love.audiodisconnected(sources) then
love.audio.setPlaybackDevice()
end
end,
}, {
__index = function(self, name)
error("Unknown event: " .. name)