Merge branch 'sdf-font-hintingmode' of https://github.com/Labrium/love into sdf-font-hintingmode

This commit is contained in:
Labrium
2023-10-05 20:12:28 -06:00
757 changed files with 443543 additions and 45188 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 &)
{
@@ -352,6 +366,52 @@ void Audio::resumeContext()
#endif
}
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 < (int) 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;
+83 -27
View File
@@ -20,6 +20,7 @@
// LOVE
#include "wrap_Audio.h"
#include "filesystem/wrap_Filesystem.h"
#include "openal/Audio.h"
#include "null/Audio.h"
@@ -47,19 +48,37 @@ 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
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");
}
}
if (lua_isstring(L, 1) || luax_istype(L, 1, love::filesystem::File::type) || luax_istype(L, 1, love::filesystem::FileData::type))
luax_convobj(L, 1, "sound", "newDecoder");
if (stype == Source::TYPE_STATIC && luax_istype(L, 1, love::sound::Decoder::type))
luax_convobj(L, 1, "sound", "newSoundData");
@@ -84,20 +103,17 @@ int w_newSource(lua_State *L)
int w_newQueueableSource(lua_State *L)
{
int samplerate = (int) luaL_checkinteger(L, 1);
int bitdepth = (int) luaL_checkinteger(L, 2);
int channels = (int) luaL_checkinteger(L, 3);
int buffers = (int) luaL_optinteger(L, 4, 0);
Source *t = nullptr;
luax_catchexcept(L, [&]() { t = instance()->newSource(samplerate, bitdepth, channels, buffers); });
luax_catchexcept(L, [&]() {
t = instance()->newSource((int)luaL_checkinteger(L, 1), (int)luaL_checkinteger(L, 2), (int)luaL_checkinteger(L, 3), (int)luaL_optinteger(L, 4, 0));
});
if (t != nullptr)
{
luax_pushtype(L, t);
t->release();
return 1;
}
else
return 0; //all argument type errors are checked in above constructor
luax_pushtype(L, t);
t->release();
return 1;
}
static std::vector<Source*> readSourceList(lua_State *L, int n)
@@ -530,10 +546,50 @@ int w_setMixWithSystem(lua_State *L)
return 1;
}
int w_getSourceCount(lua_State *L)
int w_getPlaybackDevice(lua_State* L)
{
luax_markdeprecated(L, "love.audio.getSourceCount", API_FUNCTION, DEPRECATED_RENAMED, "love.audio.getActiveSourceCount");
return w_getActiveSourceCount(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.
@@ -567,9 +623,9 @@ static const luaL_Reg functions[] =
{ "getMaxSourceEffects", w_getMaxSourceEffects },
{ "isEffectsSupported", w_isEffectsSupported },
{ "setMixWithSystem", w_setMixWithSystem },
// Deprecated
{ "getSourceCount", w_getSourceCount },
{ "getPlaybackDevice", w_getPlaybackDevice },
{ "getPlaybackDevices", w_getPlaybackDevices },
{ "setPlaybackDevice", w_setPlaybackDevice },
{ 0, 0 }
};
-11
View File
@@ -601,14 +601,6 @@ int w_Source_getType(lua_State *L)
return 1;
}
// Deprecated
int w_Source_getChannels(lua_State *L)
{
luax_markdeprecated(L, "Source:getChannels", API_METHOD, DEPRECATED_RENAMED, "Source:getChannelCount");
return w_Source_getChannelCount(L);
}
static const luaL_Reg w_Source_functions[] =
{
{ "clone", w_Source_clone },
@@ -662,9 +654,6 @@ static const luaL_Reg w_Source_functions[] =
{ "getType", w_Source_getType },
// Deprecated
{ "getChannels", w_Source_getChannels },
{ 0, 0 }
};
+7 -4
View File
@@ -31,18 +31,20 @@ namespace data
love::Type ByteData::type("ByteData", &Data::type);
ByteData::ByteData(size_t size)
ByteData::ByteData(size_t size, bool clear)
: size(size)
{
create();
memset(data, 0, size);
if (clear)
memset(data, 0, size);
}
ByteData::ByteData(const void *d, size_t size)
: size(size)
{
create();
memcpy(data, d, size);
if (d != nullptr)
memcpy(data, d, size);
}
ByteData::ByteData(void *d, size_t size, bool own)
@@ -53,7 +55,8 @@ ByteData::ByteData(void *d, size_t size, bool own)
else
{
create();
memcpy(data, d, size);
if (d != nullptr)
memcpy(data, d, size);
}
}
+1 -1
View File
@@ -35,7 +35,7 @@ public:
static love::Type type;
ByteData(size_t size);
ByteData(size_t size, bool clear = true);
ByteData(const void *d, size_t size);
ByteData(void *d, size_t size, bool own);
ByteData(const ByteData &d);
+1
View File
@@ -20,6 +20,7 @@
// LOVE
#include "CompressedData.h"
#include "common/Exception.h"
namespace love
{
+1
View File
@@ -22,6 +22,7 @@
#include "Compressor.h"
#include "common/config.h"
#include "common/int.h"
#include "common/Exception.h"
#include "libraries/lz4/lz4.h"
#include "libraries/lz4/lz4hc.h"
+139
View File
@@ -0,0 +1,139 @@
/**
* Copyright (c) 2006-2023 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 "DataStream.h"
#include "common/Exception.h"
#include "common/int.h"
#include "common/Data.h"
#include <algorithm>
namespace love
{
namespace data
{
love::Type DataStream::type("DataStream", &Stream::type);
DataStream::DataStream(Data *data)
: data(data)
, memory((const uint8 *) data->getData())
, writableMemory((uint8 *) data->getData()) // TODO: disallow writing sometimes?
, offset(0)
, size(data->getSize())
{
}
DataStream::DataStream(const DataStream &other)
: data(other.data)
, memory(other.memory)
, writableMemory(other.writableMemory)
, offset(0)
, size(other.size)
{
}
DataStream::~DataStream()
{
}
DataStream *DataStream::clone()
{
return new DataStream(*this);
}
bool DataStream::isReadable() const
{
return true;
}
bool DataStream::isWritable() const
{
return writableMemory != nullptr;
}
bool DataStream::isSeekable() const
{
return true;
}
int64 DataStream::read(void* data, int64 size)
{
if (size <= 0)
return 0;
if ((int64) offset >= getSize())
return 0;
int64 readsize = std::min<int64>(size, getSize() - offset);
memcpy(data, memory + offset, readsize);
offset += readsize;
return readsize;
}
bool DataStream::write(const void* data, int64 size)
{
if (size <= 0 || writableMemory == nullptr)
return false;
if ((int64) offset >= getSize())
return false;
int64 writesize = std::min<int64>(size, getSize() - offset);
memcpy(writableMemory + offset, data, writesize);
offset += writesize;
return true;
}
bool DataStream::flush()
{
return true;
}
int64 DataStream::getSize()
{
return size;
}
bool DataStream::seek(int64 pos, SeekOrigin origin)
{
if (origin == SEEKORIGIN_CURRENT)
pos += offset;
else if (origin == SEEKORIGIN_END)
pos += size;
if (pos < 0 || pos > (int64) size)
return false;
offset = pos;
return true;
}
int64 DataStream::tell()
{
return offset;
}
} // data
} // love
+69
View File
@@ -0,0 +1,69 @@
/**
* Copyright (c) 2006-2023 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
#include "common/Stream.h"
namespace love
{
namespace data
{
class DataStream : public love::Stream
{
public:
static love::Type type;
DataStream(Data *data);
virtual ~DataStream();
// Implements Stream.
DataStream *clone() override;
bool isReadable() const override;
bool isWritable() const override;
bool isSeekable() const override;
int64 read(void* data, int64 size) override;
bool write(const void* data, int64 size) override;
bool flush() override;
int64 getSize() override;
bool seek(int64 pos, SeekOrigin origin = SEEKORIGIN_BEGIN) override;
int64 tell() override;
private:
DataStream(const DataStream &other);
StrongRef<Data> data;
const uint8 *memory;
uint8 *writableMemory;
size_t offset;
size_t size;
}; // DataStream
} // data
} // love
+1
View File
@@ -19,6 +19,7 @@
**/
#include "HashFunction.h"
#include "common/Exception.h"
// FIXME: Probably trivial by having tole and tobe functions, which can be ifdeffed to being identity functions
#ifdef LOVE_BIG_ENDIAN
+115
View File
@@ -20,6 +20,9 @@
#include "wrap_ByteData.h"
#include "wrap_Data.h"
#include "common/config.h"
#include <algorithm>
namespace love
{
@@ -41,9 +44,121 @@ int w_ByteData_clone(lua_State *L)
return 1;
}
int w_ByteData_setString(lua_State *L)
{
Data *t = luax_checkdata(L, 1);
size_t size = 0;
const char *str = luaL_checklstring(L, 2, &size);
int64 offset = (int64)luaL_optnumber(L, 3, 0);
size = std::min(size, t->getSize());
if (size == 0)
return 0;
if (offset < 0 || offset + size > (int64) t->getSize())
return luaL_error(L, "The given string offset and size don't fit within the Data's size.");
memcpy((char *) t->getData() + (size_t) offset, str, size);
return 0;
}
template <typename T>
static int w_ByteData_setT(lua_State *L)
{
ByteData *t = luax_checkbytedata(L, 1);
int64 offset = (int64) luaL_checknumber(L, 2);
bool istable = lua_type(L, 3) == LUA_TTABLE;
int nargs = std::max(1, istable ? (int) luax_objlen(L, 3) : lua_gettop(L) - 2);
if (offset < 0 || offset + sizeof(T) * nargs > t->getSize())
return luaL_error(L, "The given offset and value parameters don't fit within the Data's size.");
auto data = (T *)((uint8 *) t->getData() + offset);
if (istable)
{
for (int i = 0; i < nargs; i++)
{
lua_rawgeti(L, 3, i + 1);
data[i] = (T) luaL_checknumber(L, -1);
lua_pop(L, 1);
}
}
else
{
for (int i = 0; i < nargs; i++)
data[i] = (T) luaL_checknumber(L, 3 + i);
}
return 0;
}
int w_ByteData_setFloat(lua_State *L)
{
return w_ByteData_setT<float>(L);
}
int w_ByteData_setDouble(lua_State *L)
{
return w_ByteData_setT<double>(L);
}
int w_ByteData_setInt8(lua_State *L)
{
return w_ByteData_setT<int8>(L);
}
int w_ByteData_setUInt8(lua_State *L)
{
return w_ByteData_setT<uint8>(L);
}
int w_ByteData_setInt16(lua_State *L)
{
return w_ByteData_setT<int16>(L);
}
int w_ByteData_setUInt16(lua_State *L)
{
return w_ByteData_setT<uint16>(L);
}
int w_ByteData_setInt32(lua_State *L)
{
return w_ByteData_setT<int32>(L);
}
int w_ByteData_setUInt32(lua_State *L)
{
return w_ByteData_setT<uint32>(L);
}
int w_ByteData_setInt64(lua_State *L)
{
return w_ByteData_setT<int64>(L);
}
int w_ByteData_setUInt64(lua_State *L)
{
return w_ByteData_setT<uint64>(L);
}
static const luaL_Reg w_ByteData_functions[] =
{
{ "clone", w_ByteData_clone },
{ "setString", w_ByteData_setString },
{ "setFloat", w_ByteData_setFloat },
{ "setDouble", w_ByteData_setDouble },
{ "setInt8", w_ByteData_setInt8 },
{ "setUInt8", w_ByteData_setUInt8 },
{ "setInt16", w_ByteData_setInt16 },
{ "setUInt16", w_ByteData_setUInt16 },
{ "setInt32", w_ByteData_setInt32 },
{ "setUInt32", w_ByteData_setUInt32 },
{ "setInt64", w_ByteData_setInt64 },
{ "setUInt64", w_ByteData_setUInt64 },
{ 0, 0 }
};
+97 -1
View File
@@ -19,6 +19,7 @@
**/
#include "wrap_Data.h"
#include "common/int.h"
// Put the Lua code directly into a raw string literal.
static const char data_lua[] =
@@ -38,7 +39,21 @@ Data *luax_checkdata(lua_State *L, int idx)
int w_Data_getString(lua_State *L)
{
Data *t = luax_checkdata(L, 1);
lua_pushlstring(L, (const char *) t->getData(), t->getSize());
int64 offset = (int64) luaL_optnumber(L, 2, 0);
int64 size = lua_isnoneornil(L, 3)
? ((int64) t->getSize() - offset)
: (int64) luaL_checknumber(L, 3);
if (size <= 0)
return luaL_error(L, "Invalid size parameter (must be greater than 0)");
if (offset < 0 || offset + size > (int64) t->getSize())
return luaL_error(L, "The given offset and size parameters don't fit within the Data's size.");
auto data = (const char *) t->getData() + offset;
lua_pushlstring(L, data, size);
return 1;
}
@@ -63,6 +78,77 @@ int w_Data_getSize(lua_State *L)
return 1;
}
template <typename T>
static int w_Data_getT(lua_State* L)
{
Data* t = luax_checkdata(L, 1);
int64 offset = (int64)luaL_checknumber(L, 2);
int count = (int)luaL_optinteger(L, 3, 1);
if (count <= 0)
return luaL_error(L, "Invalid count parameter (must be greater than 0)");
if (offset < 0 || offset + sizeof(T) * count > t->getSize())
return luaL_error(L, "The given offset and count parameters don't fit within the Data's size.");
auto data = (const T*)((uint8*)t->getData() + offset);
for (int i = 0; i < count; i++)
lua_pushnumber(L, (lua_Number)data[i]);
return count;
}
int w_Data_getFloat(lua_State* L)
{
return w_Data_getT<float>(L);
}
int w_Data_getDouble(lua_State* L)
{
return w_Data_getT<double>(L);
}
int w_Data_getInt8(lua_State* L)
{
return w_Data_getT<int8>(L);
}
int w_Data_getUInt8(lua_State* L)
{
return w_Data_getT<uint8>(L);
}
int w_Data_getInt16(lua_State* L)
{
return w_Data_getT<int16>(L);
}
int w_Data_getUInt16(lua_State* L)
{
return w_Data_getT<uint16>(L);
}
int w_Data_getInt32(lua_State* L)
{
return w_Data_getT<int32>(L);
}
int w_Data_getUInt32(lua_State* L)
{
return w_Data_getT<uint32>(L);
}
int w_Data_getInt64(lua_State* L)
{
return w_Data_getT<int64>(L);
}
int w_Data_getUInt64(lua_State* L)
{
return w_Data_getT<uint64>(L);
}
// C functions in a struct, necessary for the FFI versions of Data methods.
struct FFI_Data
{
@@ -84,6 +170,16 @@ const luaL_Reg w_Data_functions[] =
{ "getPointer", w_Data_getPointer },
{ "getFFIPointer", w_Data_getFFIPointer },
{ "getSize", w_Data_getSize },
{ "getFloat", w_Data_getFloat },
{ "getDouble", w_Data_getDouble },
{ "getInt8", w_Data_getInt8 },
{ "getUInt8", w_Data_getUInt8 },
{ "getInt16", w_Data_getInt16 },
{ "getUInt16", w_Data_getUInt16 },
{ "getInt32", w_Data_getInt32 },
{ "getUInt32", w_Data_getUInt32 },
{ "getInt64", w_Data_getInt64 },
{ "getUInt64", w_Data_getUInt64 },
{ 0, 0 }
};
+22
View File
@@ -326,6 +326,28 @@ int w_hash(lua_State *L)
int w_pack(lua_State *L)
{
if (luax_istype(L, 1, ByteData::type))
{
ByteData *d = luax_checkbytedata(L, 1);
size_t offset = (size_t) luaL_checknumber(L, 2);
const char *fmt = luaL_checkstring(L, 3);
luaL_Buffer_53 b;
lua53_str_pack(L, fmt, 4, &b);
if (offset + b.nelems > d->getSize())
{
lua53_cleanupbuffer(&b);
return luaL_error(L, "The given byte offset and pack format parameters do not fit within the ByteData's size.");
}
memcpy((uint8 *) d->getData() + offset, b.ptr, b.nelems);
lua53_cleanupbuffer(&b);
luax_pushtype(L, Data::type, d);
return 1;
}
ContainerType ctype = luax_checkcontainertype(L, 1);
const char *fmt = luaL_checkstring(L, 2);
luaL_Buffer_53 b;
-40
View File
@@ -38,46 +38,6 @@ Message::~Message()
{
}
int Message::toLua(lua_State *L)
{
luax_pushstring(L, name);
for (const Variant &v : args)
v.toLua(L);
return (int) args.size() + 1;
}
Message *Message::fromLua(lua_State *L, int n)
{
std::string name = luax_checkstring(L, n);
std::vector<Variant> vargs;
int count = lua_gettop(L) - n;
n++;
Variant varg;
for (int i = 0; i < count; i++)
{
if (lua_isnoneornil(L, n+i))
break;
luax_catchexcept(L, [&]() {
vargs.push_back(Variant::fromLua(L, n+i));
});
if (vargs.back().getType() == Variant::UNKNOWN)
{
vargs.clear();
luaL_error(L, "Argument %d can't be stored safely\nExpected boolean, number, string or userdata.", n+i);
return nullptr;
}
}
return new Message(name, vargs);
}
Event::~Event()
{
}
-3
View File
@@ -46,9 +46,6 @@ public:
Message(const std::string &name, const std::vector<Variant> &vargs = {});
~Message();
int toLua(lua_State *L);
static Message *fromLua(lua_State *L, int n);
const std::string name;
const std::vector<Variant> args;
+90 -31
View File
@@ -20,7 +20,7 @@
#include "Event.h"
#include "filesystem/DroppedFile.h"
#include "filesystem/NativeFile.h"
#include "filesystem/Filesystem.h"
#include "keyboard/sdl/Keyboard.h"
#include "joystick/JoystickModule.h"
@@ -32,9 +32,12 @@
#include "audio/Audio.h"
#include "common/config.h"
#include "timer/Timer.h"
#include "sensor/sdl/Sensor.h"
#include <cmath>
#include <SDL_version.h>
namespace love
{
namespace event
@@ -58,7 +61,7 @@ static void clampToWindow(double *x, double *y)
window->clampPositionInWindow(x, y);
}
#ifndef LOVE_MACOSX
#ifndef LOVE_MACOS
static void normalizedToDPICoords(double *x, double *y)
{
double w = 1.0, h = 1.0;
@@ -168,11 +171,11 @@ void Event::exceptionIfInRenderPass(const char *name)
{
// Some core OS graphics functionality (e.g. swap buffers on some platforms)
// happens inside SDL_PumpEvents - which is called by SDL_PollEvent and
// friends. It's probably a bad idea to call those functions while a Canvas
// friends. It's probably a bad idea to call those functions while a RT
// is active.
auto gfx = Module::getInstance<graphics::Graphics>(Module::M_GRAPHICS);
if (gfx != nullptr && gfx->isCanvasActive())
throw love::Exception("%s cannot be called while a Canvas is active in love.graphics.", name);
if (gfx != nullptr && gfx->isRenderTargetActive())
throw love::Exception("%s cannot be called while a render target is active in love.graphics.", name);
}
Message *Event::convert(const SDL_Event &e)
@@ -183,6 +186,7 @@ Message *Event::convert(const SDL_Event &e)
vargs.reserve(4);
love::filesystem::Filesystem *filesystem = nullptr;
love::sensor::Sensor *sensorInstance = nullptr;
love::keyboard::Keyboard::Key key = love::keyboard::Keyboard::KEY_UNKNOWN;
love::keyboard::Keyboard::Scancode scancode = love::keyboard::Keyboard::SCANCODE_UNKNOWN;
@@ -191,15 +195,11 @@ Message *Event::convert(const SDL_Event &e)
const char *txt2;
std::map<SDL_Keycode, love::keyboard::Keyboard::Key>::const_iterator keyit;
#ifndef LOVE_MACOSX
#ifndef LOVE_MACOS
love::touch::sdl::Touch *touchmodule = nullptr;
love::touch::Touch::TouchInfo touchinfo;
#endif
#ifdef LOVE_LINUX
static bool touchNormalizationBug = false;
#endif
switch (e.type)
{
case SDL_KEYDOWN:
@@ -312,6 +312,20 @@ Message *Event::convert(const SDL_Event &e)
case SDL_MOUSEWHEEL:
vargs.emplace_back((double) e.wheel.x);
vargs.emplace_back((double) e.wheel.y);
#if SDL_VERSION_ATLEAST(2, 0, 18)
// These values will be garbage if 2.0.18+ headers are used but a lower
// version of SDL is used at runtime, but other bits of code already
// prevent running in that situation.
vargs.emplace_back((double) e.wheel.preciseX);
vargs.emplace_back((double) e.wheel.preciseY);
#else
vargs.emplace_back((double) e.wheel.x);
vargs.emplace_back((double) e.wheel.y);
#endif
txt = e.wheel.direction == SDL_MOUSEWHEEL_FLIPPED ? "flipped" : "standard";
vargs.emplace_back(txt, strlen(txt));
msg = new Message("wheelmoved", vargs);
break;
case SDL_FINGERDOWN:
@@ -321,7 +335,7 @@ Message *Event::convert(const SDL_Event &e)
// screen events, but most touch devices in OS X aren't touch screens
// (and SDL doesn't differentiate.) Non-screen touch devices like Mac
// trackpads won't give touch coords in the window's coordinate-space.
#ifndef LOVE_MACOSX
#ifndef LOVE_MACOS
touchinfo.id = (int64) e.tfinger.fingerId;
touchinfo.x = e.tfinger.x;
touchinfo.y = e.tfinger.y;
@@ -329,22 +343,9 @@ Message *Event::convert(const SDL_Event &e)
touchinfo.dy = e.tfinger.dy;
touchinfo.pressure = e.tfinger.pressure;
#ifdef LOVE_LINUX
// FIXME: hacky workaround for SDL not normalizing touch coordinates in
// its X11 backend: https://bugzilla.libsdl.org/show_bug.cgi?id=2307
if (touchNormalizationBug || fabs(touchinfo.x) >= 1.5 || fabs(touchinfo.y) >= 1.5 || fabs(touchinfo.dx) >= 1.5 || fabs(touchinfo.dy) >= 1.5)
{
touchNormalizationBug = true;
windowToDPICoords(&touchinfo.x, &touchinfo.y);
windowToDPICoords(&touchinfo.dx, &touchinfo.dy);
}
else
#endif
{
// SDL's coords are normalized to [0, 1], but we want screen coords.
normalizedToDPICoords(&touchinfo.x, &touchinfo.y);
normalizedToDPICoords(&touchinfo.dx, &touchinfo.dy);
}
// SDL's coords are normalized to [0, 1], but we want screen coords.
normalizedToDPICoords(&touchinfo.x, &touchinfo.y);
normalizedToDPICoords(&touchinfo.dx, &touchinfo.dy);
// We need to update the love.touch.sdl internal state from here.
touchmodule = (touch::sdl::Touch *) Module::getInstance("love.touch.sdl");
@@ -382,12 +383,14 @@ Message *Event::convert(const SDL_Event &e)
case SDL_CONTROLLERBUTTONDOWN:
case SDL_CONTROLLERBUTTONUP:
case SDL_CONTROLLERAXISMOTION:
#if SDL_VERSION_ATLEAST(2, 0, 14) && defined(LOVE_ENABLE_SENSOR)
case SDL_CONTROLLERSENSORUPDATE:
#endif
msg = convertJoystickEvent(e);
break;
case SDL_WINDOWEVENT:
msg = convertWindowEvent(e);
break;
#if SDL_VERSION_ATLEAST(2, 0, 9)
case SDL_DISPLAYEVENT:
if (e.display.event == SDL_DISPLAYEVENT_ORIENTATION)
{
@@ -421,7 +424,6 @@ Message *Event::convert(const SDL_Event &e)
msg = new Message("displayrotated", vargs);
}
break;
#endif
case SDL_DROPFILE:
filesystem = Module::getInstance<filesystem::Filesystem>(Module::M_FILESYSTEM);
if (filesystem != nullptr)
@@ -436,8 +438,8 @@ Message *Event::convert(const SDL_Event &e)
}
else
{
auto *file = new love::filesystem::DroppedFile(e.drop.file);
vargs.emplace_back(&love::filesystem::DroppedFile::type, file);
auto *file = new love::filesystem::NativeFile(e.drop.file, love::filesystem::File::MODE_CLOSED);
vargs.emplace_back(&love::filesystem::NativeFile::type, file);
msg = new Message("filedropped", vargs);
file->release();
}
@@ -451,6 +453,42 @@ Message *Event::convert(const SDL_Event &e)
case SDL_APP_LOWMEMORY:
msg = new Message("lowmemory");
break;
#if SDL_VERSION_ATLEAST(2, 0, 14)
case SDL_LOCALECHANGED:
msg = new Message("localechanged");
break;
#endif
case SDL_SENSORUPDATE:
sensorInstance = Module::getInstance<sensor::Sensor>(M_SENSOR);
if (sensorInstance)
{
std::vector<void*> sensors = sensorInstance->getHandles();
for (void *s: sensors)
{
SDL_Sensor *sensor = (SDL_Sensor *) s;
SDL_SensorID id = SDL_SensorGetInstanceID(sensor);
if (e.sensor.which == id)
{
// Found sensor
const char *sensorType;
if (!sensor::Sensor::getConstant(sensor::sdl::Sensor::convert(SDL_SensorGetType(sensor)), sensorType))
sensorType = "unknown";
vargs.emplace_back(sensorType, strlen(sensorType));
// Both accelerometer and gyroscope only pass up to 3 values.
// https://github.com/libsdl-org/SDL/blob/SDL2/include/SDL_sensor.h#L81-L127
vargs.emplace_back(e.sensor.data[0]);
vargs.emplace_back(e.sensor.data[1]);
vargs.emplace_back(e.sensor.data[2]);
msg = new Message("sensorupdated", vargs);
break;
}
}
}
break;
default:
break;
}
@@ -569,6 +607,27 @@ Message *Event::convertJoystickEvent(const SDL_Event &e) const
msg = new Message("joystickremoved", vargs);
}
break;
#if SDL_VERSION_ATLEAST(2, 0, 14) && defined(LOVE_ENABLE_SENSOR)
case SDL_CONTROLLERSENSORUPDATE:
stick = joymodule->getJoystickFromID(e.csensor.which);
if (stick)
{
using Sensor = love::sensor::Sensor;
const char *sensorName;
Sensor::SensorType sensorType = love::sensor::sdl::Sensor::convert((SDL_SensorType) e.csensor.sensor);
if (!Sensor::getConstant(sensorType, sensorName))
sensorName = "unknown";
vargs.emplace_back(joysticktype, stick);
vargs.emplace_back(sensorName, strlen(sensorName));
vargs.emplace_back(e.csensor.data[0]);
vargs.emplace_back(e.csensor.data[1]);
vargs.emplace_back(e.csensor.data[2]);
msg = new Message("joysticksensorupdated", vargs);
}
break;
#endif // SDL_VERSION_ATLEAST(2, 0, 14) && defined(LOVE_ENABLE_SENSOR)
default:
break;
}
+55 -11
View File
@@ -22,9 +22,10 @@
// LOVE
#include "common/runtime.h"
#include "sdl/Event.h"
#include <algorithm>
// Shove the wrap_Event.lua code directly into a raw string literal.
static const char event_lua[] =
#include "wrap_Event.lua"
@@ -37,13 +38,23 @@ namespace event
#define instance() (Module::getInstance<Event>(Module::M_EVENT))
static int luax_pushmessage(lua_State *L, const Message &m)
{
luax_pushstring(L, m.name);
for (const Variant &v : m.args)
luax_pushvariant(L, v);
return (int) m.args.size() + 1;
}
static int w_poll_i(lua_State *L)
{
Message *m = nullptr;
if (instance()->poll(m))
if (instance()->poll(m) && m != nullptr)
{
int args = m->toLua(L);
int args = luax_pushmessage(L, *m);
m->release();
return args;
}
@@ -62,9 +73,9 @@ int w_wait(lua_State *L)
{
Message *m = nullptr;
luax_catchexcept(L, [&]() { m = instance()->wait(); });
if (m)
if (m != nullptr)
{
int args = m->toLua(L);
int args = luax_pushmessage(L, *m);
m->release();
return args;
}
@@ -74,15 +85,28 @@ int w_wait(lua_State *L)
int w_push(lua_State *L)
{
StrongRef<Message> m;
luax_catchexcept(L, [&]() { m.set(Message::fromLua(L, 1), Acquire::NORETAIN); });
std::string name = luax_checkstring(L, 1);
std::vector<Variant> vargs;
luax_pushboolean(L, m.get() != nullptr);
int nargs = lua_gettop(L);
for (int i = 2; i <= nargs; i++)
{
if (lua_isnoneornil(L, i))
break;
if (m.get() == nullptr)
return 1;
luax_catchexcept(L, [&]() { vargs.push_back(luax_checkvariant(L, i)); });
if (vargs.back().getType() == Variant::UNKNOWN)
{
vargs.clear();
return luaL_error(L, "Argument %d can't be stored safely\nExpected boolean, number, string or userdata.", i);
}
}
StrongRef<Message> m(new Message(name, vargs), Acquire::NORETAIN);
instance()->push(m);
luax_pushboolean(L, true);
return 1;
}
@@ -95,7 +119,26 @@ int w_clear(lua_State *L)
int w_quit(lua_State *L)
{
luax_catchexcept(L, [&]() {
std::vector<Variant> args = {Variant::fromLua(L, 1)};
std::vector<Variant> args;
for (int i = 1; i <= std::max(1, lua_gettop(L)); i++)
args.push_back(luax_checkvariant(L, i));
StrongRef<Message> m(new Message("quit", args), Acquire::NORETAIN);
instance()->push(m);
});
luax_pushboolean(L, true);
return 1;
}
int w_restart(lua_State *L)
{
luax_catchexcept(L, [&]() {
std::vector<Variant> args;
args.emplace_back("restart", strlen("restart"));
for (int i = 1; i <= lua_gettop(L); i++)
args.push_back(luax_checkvariant(L, i));
StrongRef<Message> m(new Message("quit", args), Acquire::NORETAIN);
instance()->push(m);
@@ -114,6 +157,7 @@ static const luaL_Reg functions[] =
{ "push", w_push },
{ "clear", w_clear },
{ "quit", w_quit },
{ "restart", w_restart },
{ 0, 0 }
};
+1
View File
@@ -24,6 +24,7 @@
// LOVE
#include "common/config.h"
#include "Event.h"
#include "common/runtime.h"
namespace love
{
+20 -53
View File
@@ -25,12 +25,17 @@ namespace love
namespace filesystem
{
love::Type File::type("File", &Object::type);
love::Type File::type("File", &Stream::type);
File::~File()
{
}
FileData *File::read()
{
return read(getSize());
}
FileData *File::read(int64 size)
{
bool isopen = isOpen();
@@ -40,7 +45,6 @@ FileData *File::read(int64 size)
int64 max = getSize();
int64 cur = tell();
size = (size == ALL) ? max : size;
if (size < 0)
throw love::Exception("Invalid read size.");
@@ -54,7 +58,7 @@ FileData *File::read(int64 size)
if (cur + size > max)
size = max - cur;
FileData *fileData = new FileData(size, getFilename());
StrongRef<FileData> fileData(new FileData(size, getFilename()), Acquire::NORETAIN);
int64 bytesRead = read(fileData->getData(), size);
if (bytesRead < 0 || (bytesRead == 0 && bytesRead != size))
@@ -65,23 +69,18 @@ FileData *File::read(int64 size)
if (bytesRead < size)
{
FileData *tmpFileData = new FileData(bytesRead, getFilename());
StrongRef<FileData> tmpFileData(new FileData(bytesRead, getFilename()), Acquire::NORETAIN);
memcpy(tmpFileData->getData(), fileData->getData(), (size_t) bytesRead);
fileData->release();
fileData = tmpFileData;
}
if (!isopen)
close();
fileData->retain();
return fileData;
}
bool File::write(const Data *data, int64 size)
{
return write(data->getData(), (size == ALL) ? data->getSize() : size);
}
std::string File::getExtension() const
{
const std::string &filename = getFilename();
@@ -93,54 +92,22 @@ std::string File::getExtension() const
return std::string();
}
bool File::getConstant(const char *in, Mode &out)
STRINGMAP_CLASS_BEGIN(File, File::Mode, File::MODE_MAX_ENUM, mode)
{
return modes.find(in, out);
{ "c", File::MODE_CLOSED },
{ "r", File::MODE_READ },
{ "w", File::MODE_WRITE },
{ "a", File::MODE_APPEND },
}
STRINGMAP_CLASS_END(File, File::Mode, File::MODE_MAX_ENUM, mode)
bool File::getConstant(Mode in, const char *&out)
STRINGMAP_CLASS_BEGIN(File, File::BufferMode, File::BUFFER_MAX_ENUM, bufferMode)
{
return modes.find(in, out);
{ "none", File::BUFFER_NONE },
{ "line", File::BUFFER_LINE },
{ "full", File::BUFFER_FULL },
}
std::vector<std::string> File::getConstants(Mode)
{
return modes.getNames();
}
bool File::getConstant(const char *in, BufferMode &out)
{
return bufferModes.find(in, out);
}
bool File::getConstant(BufferMode in, const char *&out)
{
return bufferModes.find(in, out);
}
std::vector<std::string> File::getConstants(BufferMode)
{
return bufferModes.getNames();
}
StringMap<File::Mode, File::MODE_MAX_ENUM>::Entry File::modeEntries[] =
{
{ "c", MODE_CLOSED },
{ "r", MODE_READ },
{ "w", MODE_WRITE },
{ "a", MODE_APPEND },
};
StringMap<File::Mode, File::MODE_MAX_ENUM> File::modes(File::modeEntries, sizeof(File::modeEntries));
StringMap<File::BufferMode, File::BUFFER_MAX_ENUM>::Entry File::bufferModeEntries[] =
{
{ "none", BUFFER_NONE },
{ "line", BUFFER_LINE },
{ "full", BUFFER_FULL },
};
StringMap<File::BufferMode, File::BUFFER_MAX_ENUM> File::bufferModes(File::bufferModeEntries, sizeof(File::bufferModeEntries));
STRINGMAP_CLASS_END(File, File::BufferMode, File::BUFFER_MAX_ENUM, bufferMode)
} // filesystem
} // love
+15 -78
View File
@@ -27,6 +27,7 @@
// LOVE
#include "common/Data.h"
#include "common/Object.h"
#include "common/Stream.h"
#include "common/StringMap.h"
#include "common/int.h"
#include "FileData.h"
@@ -40,7 +41,7 @@ namespace filesystem
* A File interface, providing generic means of reading from and
* writing to files.
**/
class File : public Object
class File : public Stream
{
public:
@@ -66,16 +67,19 @@ public:
BUFFER_MAX_ENUM
};
/**
* Used to indicate ALL data in a file.
**/
static const int64 ALL = -1;
/**
* Destructor.
**/
virtual ~File();
// Implements Stream.
bool isReadable() const override { return getMode() == MODE_READ; }
bool isWritable() const override { return getMode() == MODE_WRITE || getMode() == MODE_APPEND; }
bool isSeekable() const override { return isOpen(); }
using Stream::read;
using Stream::write;
/**
* Opens the file in a certain mode.
*
@@ -96,53 +100,14 @@ public:
**/
virtual bool isOpen() const = 0;
/**
* Gets the size of the file.
*
* @return The size of the file.
**/
virtual int64 getSize() = 0;
/**
* Reads data from the file and allocates a Data object.
*
* @param size The number of bytes to attempt reading, or -1 for EOF.
* @param size The number of bytes to attempt reading.
* @return A newly allocated Data object.
**/
virtual FileData *read(int64 size = ALL);
/**
* Reads data into the destination buffer.
*
* @param dst The destination buffer.
* @param size The number of bytes to attempt reading.
* @return The number of bytes actually read.
**/
virtual int64 read(void *dst, int64 size) = 0;
/**
* Writes data into the File.
*
* @param data The source buffer.
* @param size The size of the buffer.
* @return True of success, false otherwise.
**/
virtual bool write(const void *data, int64 size) = 0;
/**
* Writes a Data object into the File.
*
* @param data The data object to write into the file.
* @param size The number of bytes to attempt writing, or -1 for everything.
* @return True of success, false otherwise.
**/
virtual bool write(const Data *data, int64 size = ALL);
/**
* Flushes the currently buffered file data to disk. Only applicable in
* write mode.
**/
virtual bool flush() = 0;
FileData *read(int64 size) override;
FileData *read();
/**
* Checks whether we are currently at end-of-file.
@@ -151,21 +116,6 @@ public:
**/
virtual bool isEOF() = 0;
/**
* Gets the current position in the File.
*
* @return The current byte position in the File.
**/
virtual int64 tell() = 0;
/**
* Seeks to a certain position in the File.
*
* @param pos The byte position in the file.
* @return True on success, false otherwise.
**/
virtual bool seek(uint64 pos) = 0;
/**
* Sets the buffering mode for the file. When buffering is enabled, the file
* will not write to disk (or will pre-load data if in read mode) until the
@@ -202,21 +152,8 @@ public:
**/
virtual std::string getExtension() const;
static bool getConstant(const char *in, Mode &out);
static bool getConstant(Mode in, const char *&out);
static std::vector<std::string> getConstants(Mode);
static bool getConstant(const char *in, BufferMode &out);
static bool getConstant(BufferMode in, const char *&out);
static std::vector<std::string> getConstants(BufferMode);
private:
static StringMap<Mode, MODE_MAX_ENUM>::Entry modeEntries[];
static StringMap<Mode, MODE_MAX_ENUM> modes;
static StringMap<BufferMode, BUFFER_MAX_ENUM>::Entry bufferModeEntries[];
static StringMap<BufferMode, BUFFER_MAX_ENUM> bufferModes;
STRINGMAP_CLASS_DECLARE(Mode);
STRINGMAP_CLASS_DECLARE(BufferMode);
}; // File
+1 -4
View File
@@ -18,8 +18,7 @@
* 3. This notice may not be removed or altered from any source distribution.
**/
#ifndef LOVE_FILESYSTEM_FILE_DATA_H
#define LOVE_FILESYSTEM_FILE_DATA_H
#pragma once
// LOVE
#include "common/Data.h"
@@ -74,5 +73,3 @@ private:
} // filesystem
} // love
#endif // LOVE_FILESYSTEM_FILE_DATA_H
+109 -24
View File
@@ -26,12 +26,13 @@
#include <sys/types.h>
#include <sys/stat.h>
#if defined(LOVE_MACOSX)
#include "common/macosx.h"
#elif defined(LOVE_IOS)
#include "common/ios.h"
#if defined(LOVE_MACOS) || defined(LOVE_IOS)
#include "common/apple.h"
#include <unistd.h>
#elif defined(LOVE_WINDOWS)
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <fileapi.h>
#include "common/utf8.h"
#elif defined(LOVE_LINUX)
#include <unistd.h>
@@ -70,6 +71,14 @@ FileData *Filesystem::newFileData(const void *data, size_t size, const char *fil
}
bool Filesystem::isRealDirectory(const std::string &path) const
{
FileType ftype = FILETYPE_MAX_ENUM;
if (!getRealPathType(path, ftype))
return false;
return ftype == FILETYPE_DIRECTORY;
}
bool Filesystem::getRealPathType(const std::string &path, FileType &ftype) const
{
#ifdef LOVE_WINDOWS
// make sure non-ASCII paths work.
@@ -79,23 +88,89 @@ bool Filesystem::isRealDirectory(const std::string &path) const
if (_wstat(wpath.c_str(), &buf) != 0)
return false;
return (buf.st_mode & _S_IFDIR) == _S_IFDIR;
if ((buf.st_mode & _S_IFREG) == _S_IFREG)
ftype = FILETYPE_FILE;
else if ((buf.st_mode & _S_IFDIR) == _S_IFDIR)
ftype = FILETYPE_DIRECTORY;
else
ftype = FILETYPE_OTHER;
#else
// Assume POSIX support...
struct stat buf;
if (stat(path.c_str(), &buf) != 0)
return false;
return S_ISDIR(buf.st_mode) != 0;
if (S_ISREG(buf.st_mode))
ftype = FILETYPE_FILE;
else if (S_ISDIR(buf.st_mode))
ftype = FILETYPE_DIRECTORY;
else if (S_ISLNK(buf.st_mode))
ftype = FILETYPE_SYMLINK;
else
ftype = FILETYPE_OTHER;
#endif
return true;
}
static bool getContainingDirectory(const std::string &path, std::string &newpath)
{
size_t index = path.find_last_of("/\\");
if (index == std::string::npos)
return false;
newpath = path.substr(0, index);
// Bail if the root has been stripped out.
return newpath.find_first_of("/\\") != std::string::npos;
}
static bool createDirectoryRaw(const std::string &path)
{
#ifdef LOVE_WINDOWS
std::wstring wpath = to_widestr(path);
return CreateDirectoryW(wpath.c_str(), nullptr) != 0;
#else
return mkdir(path.c_str(), S_IRWXU) == 0;
#endif
}
bool Filesystem::createRealDirectory(const std::string &path)
{
FileType ftype = FILETYPE_MAX_ENUM;
if (getRealPathType(path, ftype))
return ftype == FILETYPE_DIRECTORY;
std::vector<std::string> createpaths = {path};
// Find the deepest subdirectory in the given path that actually exists.
while (true)
{
std::string subpath;
if (!getContainingDirectory(createpaths[0], subpath))
break;
if (isRealDirectory(subpath))
break;
createpaths.insert(createpaths.begin(), subpath);
}
// Try to create missing subdirectories starting from that existing one.
for (const std::string &p : createpaths)
{
if (!createDirectoryRaw(p))
return false;
}
return true;
}
std::string Filesystem::getExecutablePath() const
{
#if defined(LOVE_MACOSX)
return love::macosx::getExecutablePath();
#elif defined(LOVE_IOS)
return love::ios::getExecutablePath();
#if defined(LOVE_MACOS) || defined(LOVE_IOS)
return love::apple::getExecutablePath();
#elif defined(LOVE_WINDOWS)
wchar_t buffer[MAX_PATH + 1] = {0};
@@ -120,30 +195,40 @@ std::string Filesystem::getExecutablePath() const
#endif
}
bool Filesystem::getConstant(const char *in, FileType &out)
STRINGMAP_CLASS_BEGIN(Filesystem, Filesystem::FileType, Filesystem::FILETYPE_MAX_ENUM, fileType)
{
return fileTypes.find(in, out);
{ "file", Filesystem::FILETYPE_FILE },
{ "directory", Filesystem::FILETYPE_DIRECTORY },
{ "symlink", Filesystem::FILETYPE_SYMLINK },
{ "other", Filesystem::FILETYPE_OTHER },
}
STRINGMAP_CLASS_END(Filesystem, Filesystem::FileType, Filesystem::FILETYPE_MAX_ENUM, fileType)
bool Filesystem::getConstant(FileType in, const char *&out)
STRINGMAP_CLASS_BEGIN(Filesystem, Filesystem::CommonPath, Filesystem::COMMONPATH_MAX_ENUM, commonPath)
{
return fileTypes.find(in, out);
{ "appsavedir", Filesystem::COMMONPATH_APP_SAVEDIR },
{ "appdocuments", Filesystem::COMMONPATH_APP_DOCUMENTS },
{ "userhome", Filesystem::COMMONPATH_USER_HOME },
{ "userappdata", Filesystem::COMMONPATH_USER_APPDATA },
{ "userdesktop", Filesystem::COMMONPATH_USER_DESKTOP },
{ "userdocuments", Filesystem::COMMONPATH_USER_DOCUMENTS },
}
STRINGMAP_CLASS_END(Filesystem, Filesystem::CommonPath, Filesystem::COMMONPATH_MAX_ENUM, commonPath)
std::vector<std::string> Filesystem::getConstants(FileType)
STRINGMAP_CLASS_BEGIN(Filesystem, Filesystem::MountPermissions, Filesystem::MOUNT_PERMISSIONS_MAX_ENUM, mountPermissions)
{
return fileTypes.getNames();
{ "read", Filesystem::MOUNT_PERMISSIONS_READ },
{ "readwrite", Filesystem::MOUNT_PERMISSIONS_READWRITE },
}
STRINGMAP_CLASS_END(Filesystem, Filesystem::MountPermissions, Filesystem::MOUNT_PERMISSIONS_MAX_ENUM, mountPermissions)
StringMap<Filesystem::FileType, Filesystem::FILETYPE_MAX_ENUM>::Entry Filesystem::fileTypeEntries[] =
STRINGMAP_CLASS_BEGIN(Filesystem, Filesystem::LoadMode, Filesystem::LOADMODE_MAX_ENUM, loadMode)
{
{ "file", FILETYPE_FILE },
{ "directory", FILETYPE_DIRECTORY },
{ "symlink", FILETYPE_SYMLINK },
{ "other", FILETYPE_OTHER },
};
StringMap<Filesystem::FileType, Filesystem::FILETYPE_MAX_ENUM> Filesystem::fileTypes(Filesystem::fileTypeEntries, sizeof(Filesystem::fileTypeEntries));
{ "b", Filesystem::LOADMODE_BINARY},
{ "t", Filesystem::LOADMODE_TEXT },
{ "bt", Filesystem::LOADMODE_ANY }
}
STRINGMAP_CLASS_END(Filesystem, Filesystem::LoadMode, Filesystem::LOADMODE_MAX_ENUM, loadMode)
} // filesystem
} // love
+61 -12
View File
@@ -41,7 +41,7 @@
# define LOVE_PATH_SEPARATOR "/"
# define LOVE_MAX_PATH _MAX_PATH
#else
# if defined(LOVE_MACOSX) || defined(LOVE_IOS)
# if defined(LOVE_MACOS) || defined(LOVE_IOS)
# define LOVE_APPDATA_FOLDER "LOVE"
# elif defined(LOVE_LINUX)
# define LOVE_APPDATA_FOLDER "love"
@@ -71,12 +71,39 @@ public:
FILETYPE_MAX_ENUM
};
enum CommonPath
{
COMMONPATH_APP_SAVEDIR,
COMMONPATH_APP_DOCUMENTS,
COMMONPATH_USER_HOME,
COMMONPATH_USER_APPDATA,
COMMONPATH_USER_DESKTOP,
COMMONPATH_USER_DOCUMENTS,
COMMONPATH_MAX_ENUM
};
enum MountPermissions
{
MOUNT_PERMISSIONS_READ,
MOUNT_PERMISSIONS_READWRITE,
MOUNT_PERMISSIONS_MAX_ENUM
};
enum LoadMode
{
LOADMODE_BINARY,
LOADMODE_TEXT,
LOADMODE_ANY,
LOADMODE_MAX_ENUM
};
struct Info
{
// Numbers will be -1 if they cannot be determined.
int64 size;
int64 modtime;
FileType type;
bool readonly;
};
static love::Type type;
@@ -136,13 +163,19 @@ public:
virtual bool mount(const char *archive, const char *mountpoint, bool appendToPath = false) = 0;
virtual bool mount(Data *data, const char *archivename, const char *mountpoint, bool appendToPath = false) = 0;
virtual bool mountFullPath(const char *archive, const char *mountpoint, MountPermissions permissions, bool appendToPath = false) = 0;
virtual bool mountCommonPath(CommonPath path, const char *mountpoint, MountPermissions permissions, bool appendToPath = false) = 0;
virtual bool unmount(const char *archive) = 0;
virtual bool unmount(Data *data) = 0;
virtual bool unmount(CommonPath path) = 0;
virtual bool unmountFullPath(const char *fullpath) = 0;
/**
* Creates a new file.
* Opens a new File object from the specified path, using the given mode.
**/
virtual File *newFile(const char *filename) const = 0;
virtual File *openFile(const char *filename, File::Mode mode) const = 0;
/**
* Creates a new FileData object. Data will be copied.
@@ -152,6 +185,11 @@ public:
**/
virtual FileData *newFileData(const void *data, size_t size, const char *filename) const;
/**
* Gets the full path for the given common path.
*/
virtual std::string getFullCommonPath(CommonPath path) = 0;
/**
* Gets the current working directory.
**/
@@ -172,7 +210,7 @@ public:
/**
* Gets the full path of the save folder.
**/
virtual const char *getSaveDirectory() = 0;
virtual std::string getSaveDirectory() = 0;
/**
* Gets the full path to the directory containing the game source.
@@ -186,6 +224,11 @@ public:
**/
virtual std::string getRealDirectory(const char *filename) const = 0;
/**
* Gets whether anything exists at the specified path.
**/
virtual bool exists(const char *filepath) const = 0;
/**
* Gets information about the item at the specified filepath. Returns false
* if nothing exists at the path.
@@ -209,7 +252,8 @@ public:
* @param filename The name of the file to read from.
* @param size The size in bytes of the data to read.
**/
virtual FileData *read(const char *filename, int64 size = File::ALL) const = 0;
virtual FileData *read(const char *filename, int64 size) const = 0;
virtual FileData *read(const char *filename) const = 0;
/**
* Write data to a file.
@@ -231,7 +275,7 @@ public:
* This "native" method returns a table of all
* files in a given directory.
**/
virtual void getDirectoryItems(const char *dir, std::vector<std::string> &items) = 0;
virtual bool getDirectoryItems(const char *dir, std::vector<std::string> &items) = 0;
/**
* Enable or disable symbolic link support in love.filesystem.
@@ -258,23 +302,28 @@ public:
**/
virtual bool isRealDirectory(const std::string &path) const;
/**
* Recursively creates a directory at the given full OS-dependent path.
**/
virtual bool createRealDirectory(const std::string &path);
/**
* Gets the full platform-dependent path to the executable.
**/
virtual std::string getExecutablePath() const;
static bool getConstant(const char *in, FileType &out);
static bool getConstant(FileType in, const char *&out);
static std::vector<std::string> getConstants(FileType);
STRINGMAP_CLASS_DECLARE(FileType);
STRINGMAP_CLASS_DECLARE(CommonPath);
STRINGMAP_CLASS_DECLARE(MountPermissions);
STRINGMAP_CLASS_DECLARE(LoadMode);
private:
bool getRealPathType(const std::string &path, FileType &ftype) const;
// Should we save external or internal for Android
bool useExternal;
static StringMap<FileType, FILETYPE_MAX_ENUM>::Entry fileTypeEntries[];
static StringMap<FileType, FILETYPE_MAX_ENUM> fileTypes;
}; // Filesystem
} // filesystem
@@ -19,9 +19,13 @@
**/
// LOVE
#include "DroppedFile.h"
#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>
@@ -37,33 +41,69 @@ namespace love
namespace filesystem
{
love::Type DroppedFile::type("DroppedFile", &File::type);
love::Type NativeFile::type("NativeFile", &File::type);
DroppedFile::DroppedFile(const std::string &filename)
NativeFile::NativeFile(const std::string &filename, Mode mode)
: filename(filename)
, file(nullptr)
, mode(MODE_CLOSED)
, bufferMode(BUFFER_NONE)
, bufferSize(0)
{
if (!open(mode))
throw love::Exception("Could not open file at path %s", filename.c_str());
}
DroppedFile::~DroppedFile()
NativeFile::NativeFile(const NativeFile &other)
: filename(other.filename)
, file(nullptr)
, mode(MODE_CLOSED)
, bufferMode(other.bufferMode)
, bufferSize(other.bufferSize)
{
if (!open(other.mode))
throw love::Exception("Could not open file at path %s", filename.c_str());
}
NativeFile::~NativeFile()
{
if (mode != MODE_CLOSED)
close();
}
bool DroppedFile::open(Mode newmode)
NativeFile *NativeFile::clone()
{
return new NativeFile(*this);
}
bool NativeFile::open(Mode newmode)
{
if (newmode == MODE_CLOSED)
{
close();
return true;
}
// File already open?
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);
@@ -88,7 +128,7 @@ bool DroppedFile::open(Mode newmode)
return file != nullptr;
}
bool DroppedFile::close()
bool NativeFile::close()
{
if (file == nullptr || fclose(file) != 0)
return false;
@@ -99,12 +139,12 @@ bool DroppedFile::close()
return true;
}
bool DroppedFile::isOpen() const
bool NativeFile::isOpen() const
{
return mode != MODE_CLOSED && file != nullptr;
}
int64 DroppedFile::getSize()
int64 NativeFile::getSize()
{
int fd = file ? fileno(file) : -1;
@@ -146,7 +186,7 @@ int64 DroppedFile::getSize()
#endif
}
int64 DroppedFile::read(void *dst, int64 size)
int64 NativeFile::read(void *dst, int64 size)
{
if (!file || mode != MODE_READ)
throw love::Exception("File is not opened for reading.");
@@ -159,7 +199,7 @@ int64 DroppedFile::read(void *dst, int64 size)
return (int64) read;
}
bool DroppedFile::write(const void *data, int64 size)
bool NativeFile::write(const void *data, int64 size)
{
if (!file || (mode != MODE_WRITE && mode != MODE_APPEND))
throw love::Exception("File is not opened for writing.");
@@ -172,7 +212,7 @@ bool DroppedFile::write(const void *data, int64 size)
return written == size;
}
bool DroppedFile::flush()
bool NativeFile::flush()
{
if (!file || (mode != MODE_WRITE && mode != MODE_APPEND))
throw love::Exception("File is not opened for writing.");
@@ -180,12 +220,12 @@ bool DroppedFile::flush()
return fflush(file) == 0;
}
bool DroppedFile::isEOF()
bool NativeFile::isEOF()
{
return file == nullptr || tell() >= getSize();
}
int64 DroppedFile::tell()
int64 NativeFile::tell()
{
if (file == nullptr)
return -1;
@@ -197,19 +237,26 @@ int64 DroppedFile::tell()
#endif
}
bool DroppedFile::seek(uint64 pos)
bool NativeFile::seek(int64 pos, SeekOrigin origin)
{
if (file == nullptr)
return false;
int forigin = SEEK_SET;
if (origin == SEEKORIGIN_CURRENT)
forigin = SEEK_CUR;
else if (origin == SEEKORIGIN_END)
forigin = SEEK_END;
// TODO
#ifdef LOVE_WINDOWS
return _fseeki64(file, (int64) pos, SEEK_SET) == 0;
return _fseeki64(file, pos, forigin) == 0;
#else
return fseeko(file, (off_t) pos, SEEK_SET) == 0;
return fseeko(file, (off_t) pos, forigin) == 0;
#endif
}
bool DroppedFile::setBuffer(BufferMode bufmode, int64 size)
bool NativeFile::setBuffer(BufferMode bufmode, int64 size)
{
if (size < 0)
return false;
@@ -218,7 +265,7 @@ bool DroppedFile::setBuffer(BufferMode bufmode, int64 size)
size = 0;
// If the file isn't open, we'll make sure the buffer values are set in
// DroppedFile::open.
// NativeFile::open.
if (!isOpen())
{
bufferMode = bufmode;
@@ -250,23 +297,23 @@ bool DroppedFile::setBuffer(BufferMode bufmode, int64 size)
return true;
}
File::BufferMode DroppedFile::getBuffer(int64 &size) const
File::BufferMode NativeFile::getBuffer(int64 &size) const
{
size = bufferSize;
return bufferMode;
}
const std::string &DroppedFile::getFilename() const
const std::string &NativeFile::getFilename() const
{
return filename;
}
File::Mode DroppedFile::getMode() const
File::Mode NativeFile::getMode() const
{
return mode;
}
const char *DroppedFile::getModeString(Mode mode)
const char *NativeFile::getModeString(Mode mode)
{
switch (mode)
{
@@ -18,9 +18,6 @@
* 3. This notice may not be removed or altered from any source distribution.
**/
#ifndef LOVE_FILESYSTEM_DROPPED_FILE_H
#define LOVE_FILESYSTEM_DROPPED_FILE_H
// LOVE
#include "common/config.h"
#include "File.h"
@@ -34,17 +31,25 @@ namespace filesystem
{
/**
* File which is created when a user drags and drops an actual file onto the
* LOVE game. Uses C's stdio. Filenames are system-dependent full paths.
* File which uses C's stdio. Filenames are system-dependent full paths.
**/
class DroppedFile : public File
class NativeFile : public File
{
public:
static love::Type type;
DroppedFile(const std::string &filename);
virtual ~DroppedFile();
NativeFile(const std::string &filename, Mode mode);
virtual ~NativeFile();
// Implements Stream.
NativeFile *clone() override;
int64 read(void* dst, int64 size) override;
bool write(const void* data, int64 size) override;
bool flush() override;
int64 getSize() override;
int64 tell() override;
bool seek(int64 pos, SeekOrigin origin) override;
// Implements File.
using File::read;
@@ -52,13 +57,7 @@ public:
bool open(Mode mode) override;
bool close() override;
bool isOpen() const override;
int64 getSize() override;
int64 read(void *dst, int64 size) override;
bool write(const void *data, int64 size) override;
bool flush() override;
bool isEOF() override;
int64 tell() override;
bool seek(uint64 pos) override;
bool setBuffer(BufferMode bufmode, int64 size) override;
BufferMode getBuffer(int64 &size) const override;
Mode getMode() const override;
@@ -66,6 +65,8 @@ public:
private:
NativeFile(const NativeFile &other);
static const char *getModeString(Mode mode);
std::string filename;
@@ -77,9 +78,7 @@ private:
BufferMode bufferMode;
int64 bufferSize;
}; // DroppedFile
}; // NativeFile
} // filesystem
} // love
#endif // LOVE_FILESYSTEM_DROPPED_FILE_H
+42 -28
View File
@@ -32,18 +32,35 @@ namespace love
namespace filesystem
{
extern bool hack_setupWriteDirectory();
namespace physfs
{
File::File(const std::string &filename)
static bool setupWriteDirectory()
{
auto fs = Module::getInstance<love::filesystem::Filesystem>(Module::M_FILESYSTEM);
return fs != nullptr && fs->setupWriteDirectory();
}
File::File(const std::string &filename, Mode mode)
: filename(filename)
, file(nullptr)
, mode(MODE_CLOSED)
, bufferMode(BUFFER_NONE)
, bufferSize(0)
{
if (!open(mode))
throw love::Exception("Could not open file at path %s", filename.c_str());
}
File::File(const File &other)
: filename(other.filename)
, file(nullptr)
, mode(MODE_CLOSED)
, bufferMode(other.bufferMode)
, bufferSize(other.bufferSize)
{
if (!open(other.mode))
throw love::Exception("Could not open file at path %s", filename.c_str());
}
File::~File()
@@ -52,10 +69,18 @@ File::~File()
close();
}
File *File::clone()
{
return new File(*this);
}
bool File::open(Mode mode)
{
if (mode == MODE_CLOSED)
{
close();
return true;
}
if (!PHYSFS_isInit())
throw love::Exception("PhysFS is not initialized.");
@@ -65,14 +90,13 @@ bool File::open(Mode mode)
throw love::Exception("Could not open file %s. Does not exist.", filename.c_str());
// Check whether the write directory is set.
if ((mode == MODE_APPEND || mode == MODE_WRITE) && (PHYSFS_getWriteDir() == nullptr) && !hack_setupWriteDirectory())
if ((mode == MODE_APPEND || mode == MODE_WRITE) && !setupWriteDirectory())
throw love::Exception("Could not set write directory.");
// File already open?
if (file != nullptr)
return false;
PHYSFS_getLastErrorCode();
PHYSFS_File *handle = nullptr;
switch (mode)
@@ -148,10 +172,6 @@ int64 File::read(void *dst, int64 size)
if (!file || mode != MODE_READ)
throw love::Exception("File is not opened for reading.");
int64 max = (int64)PHYSFS_fileLength(file);
size = (size == ALL) ? max : size;
size = (size > max) ? max : size;
if (size < 0)
throw love::Exception("Invalid read size.");
@@ -191,26 +211,9 @@ bool File::flush()
return PHYSFS_flush(file) != 0;
}
#ifdef LOVE_WINDOWS
// MSVC doesn't like the 'this' keyword
// well, we'll use 'that'.
// It zigs, we zag.
inline bool test_eof(File *that, PHYSFS_File *)
{
int64 pos = that->tell();
int64 size = that->getSize();
return pos == -1 || size == -1 || pos >= size;
}
#else
inline bool test_eof(File *, PHYSFS_File *file)
{
return PHYSFS_eof(file);
}
#endif
bool File::isEOF()
{
return file == nullptr || test_eof(this, file);
return file == nullptr || PHYSFS_eof(file);
}
int64 File::tell()
@@ -221,8 +224,19 @@ int64 File::tell()
return (int64) PHYSFS_tell(file);
}
bool File::seek(uint64 pos)
bool File::seek(int64 pos, SeekOrigin origin)
{
if (file != nullptr)
{
if (origin == SEEKORIGIN_CURRENT)
pos += tell();
else if (origin == SEEKORIGIN_END)
pos += getSize();
}
if (pos < 0)
return false;
return file != nullptr && PHYSFS_seek(file, (PHYSFS_uint64) pos) != 0;
}
+12 -7
View File
@@ -46,23 +46,26 @@ public:
* Constructs an File with the given ilename.
* @param filename The relative filepath of the file to load.
**/
File(const std::string &filename);
File(const std::string &filename, Mode mode);
virtual ~File();
// Implements Stream.
File *clone() override;
int64 read(void* dst, int64 size) override;
bool write(const void* data, int64 size) override;
bool flush() override;
int64 getSize() override;
bool seek(int64 pos, SeekOrigin origin) override;
int64 tell() override;
// Implements love::filesystem::File.
using love::filesystem::File::read;
using love::filesystem::File::write;
bool open(Mode mode) override;
bool close() override;
bool isOpen() const override;
int64 getSize() override;
virtual int64 read(void *dst, int64 size) override;
bool write(const void *data, int64 size) override;
bool flush() override;
bool isEOF() override;
int64 tell() override;
bool seek(uint64 pos) override;
bool setBuffer(BufferMode bufmode, int64 size) override;
BufferMode getBuffer(int64 &size) const override;
Mode getMode() const override;
@@ -70,6 +73,8 @@ public:
private:
File(const File &other);
// filename
std::string filename;
+388 -245
View File
@@ -36,17 +36,29 @@
// Using this instead of boost::filesystem which totally
// cramped our style.
#ifdef LOVE_WINDOWS
# define WIN32_LEAN_AND_MEAN
# include <windows.h>
# include <direct.h>
# include <initguid.h>
# include <Shlobj.h>
# include <Knownfolders.h>
#else
# include <sys/param.h>
# include <unistd.h>
#endif
#if defined(LOVE_IOS) || defined(LOVE_MACOS)
# include "common/apple.h"
#endif
#ifdef LOVE_IOS
# include "common/ios.h"
#endif
#ifdef LOVE_MACOS
# include "common/macos.h"
#endif
#include <string>
#ifdef LOVE_ANDROID
@@ -54,44 +66,6 @@
#include "common/android.h"
#endif
namespace
{
size_t getDriveDelim(const std::string &input)
{
for (size_t i = 0; i < input.size(); ++i)
if (input[i] == '/' || input[i] == '\\')
return i;
// Something's horribly wrong
return 0;
}
std::string getDriveRoot(const std::string &input)
{
return input.substr(0, getDriveDelim(input)+1);
}
std::string skipDriveRoot(const std::string &input)
{
return input.substr(getDriveDelim(input)+1);
}
std::string normalize(const std::string &input)
{
std::stringstream out;
bool seenSep = false, isSep = false;
for (size_t i = 0; i < input.size(); ++i)
{
isSep = (input[i] == LOVE_PATH_SEPARATOR[0]);
if (!isSep || !seenSep)
out << input[i];
seenSep = isSep;
}
return out.str();
}
}
namespace love
{
namespace filesystem
@@ -99,9 +73,46 @@ namespace filesystem
namespace physfs
{
static std::string normalize(const std::string &input)
{
std::stringstream out;
bool seenSep = false, isSep = false;
for (size_t i = 0; i < input.size(); ++i)
{
isSep = (input[i] == LOVE_PATH_SEPARATOR[0]);
if (!isSep || !seenSep)
out << input[i];
seenSep = isSep;
}
return out.str();
}
static const Filesystem::CommonPath appCommonPaths[] =
{
Filesystem::COMMONPATH_APP_SAVEDIR,
Filesystem::COMMONPATH_APP_DOCUMENTS
};
static bool isAppCommonPath(Filesystem::CommonPath path)
{
switch (path)
{
case Filesystem::COMMONPATH_APP_SAVEDIR:
case Filesystem::COMMONPATH_APP_DOCUMENTS:
return true;
default:
return false;
}
}
Filesystem::Filesystem()
: fused(false)
: appendIdentityToPath(false)
, fused(false)
, fusedSet(false)
, fullPaths()
, commonPathMountInfo()
, saveDirectoryNeedsMounting(false)
{
requirePath = {"?.lua", "?/init.lua"};
cRequirePath = {"??"};
@@ -155,67 +166,65 @@ bool Filesystem::setIdentity(const char *ident, bool appendToPath)
if (!PHYSFS_isInit())
return false;
std::string old_save_path = save_path_full;
if (ident == nullptr || strlen(ident) == 0)
return false;
// Store the save directory.
save_identity = std::string(ident);
// Validate whether re-mounting will work.
for (CommonPath p : appCommonPaths)
{
if (!commonPathMountInfo[p].mounted)
continue;
// Generate the relative path to the game save folder.
save_path_relative = std::string(LOVE_APPDATA_PREFIX LOVE_APPDATA_FOLDER LOVE_PATH_SEPARATOR) + save_identity;
// If a file is still open, unmount will fail.
std::string fullPath = getFullCommonPath(p);
if (!fullPath.empty() && !PHYSFS_canUnmount(fullPath.c_str()))
return false;
}
// Generate the full path to the game save folder.
save_path_full = std::string(getAppdataDirectory()) + std::string(LOVE_PATH_SEPARATOR);
if (fused)
save_path_full += std::string(LOVE_APPDATA_PREFIX) + save_identity;
else
save_path_full += save_path_relative;
bool oldMountedCommonPaths[COMMONPATH_MAX_ENUM] = {false};
save_path_full = normalize(save_path_full);
// We don't want old save paths to accumulate when we set a new identity.
for (CommonPath p : appCommonPaths)
{
oldMountedCommonPaths[p] = commonPathMountInfo[p].mounted;
if (commonPathMountInfo[p].mounted)
unmount(p);
}
#ifdef LOVE_ANDROID
if (save_identity == "")
save_identity = "unnamed";
// These will be re-populated by getFullCommonPath.
for (CommonPath p : appCommonPaths)
fullPaths[p].clear();
std::string storage_path;
if (isAndroidSaveExternal())
storage_path = SDL_AndroidGetExternalStoragePath();
else
storage_path = SDL_AndroidGetInternalStoragePath();
// Store the save directory. getFullCommonPath(COMMONPATH_APP_*) uses this.
saveIdentity = std::string(ident);
appendIdentityToPath = appendToPath;
std::string save_directory = storage_path + "/save";
// Try to mount as readwrite without creating missing directories in the
// path hierarchy. If this fails, setupWriteDirectory will attempt to create
// them and try again.
// This is done so the save directory is only created on-demand.
if (!mountCommonPathInternal(COMMONPATH_APP_SAVEDIR, nullptr, MOUNT_PERMISSIONS_READWRITE, appendToPath, false))
saveDirectoryNeedsMounting = true;
save_path_full = storage_path + std::string("/save/") + save_identity;
if (!love::android::directoryExists(save_path_full.c_str()) &&
!love::android::mkdir(save_path_full.c_str()))
SDL_Log("Error: Could not create save directory %s!", save_path_full.c_str());
#endif
// We now have something like:
// save_identity: game
// save_path_relative: ./LOVE/game
// save_path_full: C:\Documents and Settings\user\Application Data/LOVE/game
// We don't want old read-only save paths to accumulate when we set a new
// identity.
if (!old_save_path.empty())
PHYSFS_unmount(old_save_path.c_str());
// Try to add the save directory to the search path.
// (No error on fail, it means that the path doesn't exist).
PHYSFS_mount(save_path_full.c_str(), nullptr, appendToPath);
// HACK: This forces setupWriteDirectory to be called the next time a file
// is opened for writing - otherwise it won't be called at all if it was
// already called at least once before.
PHYSFS_setWriteDir(nullptr);
// Mount any other app common paths with directory creation immediately
// instead of on-demand, since to get to this point they would have to be
// explicitly mounted already beforehand.
for (CommonPath p : appCommonPaths)
{
if (oldMountedCommonPaths[p] && p != COMMONPATH_APP_SAVEDIR)
{
// TODO: error handling?
auto info = commonPathMountInfo[p];
mountCommonPathInternal(p, info.mountPoint.c_str(), info.permissions, appendToPath, true);
}
}
return true;
}
const char *Filesystem::getIdentity() const
{
return save_identity.c_str();
return saveIdentity.c_str();
}
bool Filesystem::setSource(const char *source)
@@ -224,7 +233,7 @@ bool Filesystem::setSource(const char *source)
return false;
// Check whether directory is already set.
if (!game_source.empty())
if (!gameSource.empty())
return false;
std::string new_search_path = source;
@@ -233,8 +242,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;
@@ -260,34 +267,21 @@ 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))
{
@@ -311,17 +305,16 @@ bool Filesystem::setSource(const char *source)
}
return true;
}
#endif
// Save the game source.
game_source = new_search_path;
gameSource = new_search_path;
return true;
}
const char *Filesystem::getSource() const
{
return game_source.c_str();
return gameSource.c_str();
}
bool Filesystem::setupWriteDirectory()
@@ -329,54 +322,19 @@ bool Filesystem::setupWriteDirectory()
if (!PHYSFS_isInit())
return false;
// These must all be set.
if (save_identity.empty() || save_path_full.empty() || save_path_relative.empty())
if (!saveDirectoryNeedsMounting)
return true;
if (saveIdentity.empty())
return false;
// We need to make sure the write directory is created. To do that, we also
// need to make sure all its parent directories are also created.
std::string temp_writedir = getDriveRoot(save_path_full);
std::string temp_createdir = skipDriveRoot(save_path_full);
// On some sandboxed platforms, physfs will break when its write directory
// is the root of the drive and it tries to create a folder (even if the
// folder's path is in a writable location.) If the user's home folder is
// in the save path, we'll try starting from there instead.
if (save_path_full.find(getUserDirectory()) == 0)
{
temp_writedir = getUserDirectory();
temp_createdir = save_path_full.substr(getUserDirectory().length());
// Strip leading '/' characters from the path we want to create.
size_t startpos = temp_createdir.find_first_not_of('/');
if (startpos != std::string::npos)
temp_createdir = temp_createdir.substr(startpos);
}
// Set either '/' or the user's home as a writable directory.
// (We must create the save folder before mounting it).
if (!PHYSFS_setWriteDir(temp_writedir.c_str()))
// Only the save directory is mounted on-demand if it doesn't exist yet.
// Other app common paths are immediately re-mounted in setIdentity.
bool createdir = true;
if (!mountCommonPathInternal(COMMONPATH_APP_SAVEDIR, nullptr, MOUNT_PERMISSIONS_READWRITE, appendIdentityToPath, createdir))
return false;
// Create the save folder. (We're now "at" either '/' or the user's home).
if (!createDirectory(temp_createdir.c_str()))
{
// Clear the write directory in case of error.
PHYSFS_setWriteDir(nullptr);
return false;
}
// Set the final write directory.
if (!PHYSFS_setWriteDir(save_path_full.c_str()))
return false;
// Add the directory. (Will not be readded if already present).
if (!PHYSFS_mount(save_path_full.c_str(), nullptr, 0))
{
PHYSFS_setWriteDir(nullptr); // Clear the write directory in case of error.
return false;
}
saveDirectoryNeedsMounting = false;
return true;
}
@@ -413,17 +371,52 @@ bool Filesystem::mount(const char *archive, const char *mountpoint, bool appendT
// Always disallow mounting of files inside the game source, since it
// won't work anyway if the game source is a zipped .love file.
if (realPath.find(game_source) == 0)
if (realPath.find(gameSource) == 0)
return false;
realPath += LOVE_PATH_SEPARATOR;
realPath += archive;
}
if (realPath.length() == 0)
return mountFullPath(realPath.c_str(), mountpoint, MOUNT_PERMISSIONS_READ, appendToPath);
}
bool Filesystem::mountFullPath(const char *archive, const char *mountpoint, MountPermissions permissions, bool appendToPath)
{
if (!PHYSFS_isInit() || !archive)
return false;
return PHYSFS_mount(realPath.c_str(), mountpoint, appendToPath) != 0;
if (permissions == MOUNT_PERMISSIONS_READWRITE)
return PHYSFS_mountRW(archive, mountpoint, appendToPath) != 0;
return PHYSFS_mount(archive, mountpoint, appendToPath) != 0;
}
bool Filesystem::mountCommonPathInternal(CommonPath path, const char *mountpoint, MountPermissions permissions, bool appendToPath, bool createDir)
{
std::string fullpath = getFullCommonPath(path);
if (fullpath.empty())
return false;
if (createDir && isAppCommonPath(path) && !isRealDirectory(fullpath))
{
if (!createRealDirectory(fullpath))
return false;
}
if (mountFullPath(fullpath.c_str(), mountpoint, permissions, appendToPath))
{
std::string mp = mountpoint != nullptr ? mountpoint : "/";
commonPathMountInfo[path] = {true, mp, permissions};
return true;
}
return false;
}
bool Filesystem::mountCommonPath(CommonPath path, const char *mountpoint, MountPermissions permissions, bool appendToPath)
{
return mountCommonPathInternal(path, mountpoint, permissions, appendToPath, true);
}
bool Filesystem::mount(Data *data, const char *archivename, const char *mountpoint, bool appendToPath)
@@ -453,42 +446,52 @@ bool Filesystem::unmount(const char *archive)
return true;
}
std::string realPath;
std::string sourceBase = getSourceBaseDirectory();
// Check whether the given archive path is in the list of allowed full paths.
auto it = std::find(allowedMountPaths.begin(), allowedMountPaths.end(), archive);
if (it != allowedMountPaths.end())
realPath = *it;
else if (isFused() && sourceBase.compare(archive) == 0)
{
// Special case: if the game is fused and the archive is the source's
// base directory, unmount it even though it's outside of the save dir.
realPath = sourceBase;
}
else
{
// Not allowed for safety reasons.
if (strlen(archive) == 0 || strstr(archive, "..") || strcmp(archive, "/") == 0)
return false;
return unmountFullPath(archive);
const char *realDir = PHYSFS_getRealDir(archive);
if (!realDir)
return false;
std::string sourceBase = getSourceBaseDirectory();
if (isFused() && sourceBase.compare(archive) == 0)
return unmountFullPath(archive);
realPath = realDir;
realPath += LOVE_PATH_SEPARATOR;
realPath += archive;
}
if (strlen(archive) == 0 || strstr(archive, "..") || strcmp(archive, "/") == 0)
return false;
const char *mountPoint = PHYSFS_getMountPoint(realPath.c_str());
if (!mountPoint)
const char *realDir = PHYSFS_getRealDir(archive);
if (!realDir)
return false;
std::string realPath = realDir;
realPath += LOVE_PATH_SEPARATOR;
realPath += archive;
if (PHYSFS_getMountPoint(realPath.c_str()) == nullptr)
return false;
return PHYSFS_unmount(realPath.c_str()) != 0;
}
bool Filesystem::unmountFullPath(const char *fullpath)
{
if (!PHYSFS_isInit() || !fullpath)
return false;
return PHYSFS_unmount(fullpath) != 0;
}
bool Filesystem::unmount(CommonPath path)
{
std::string fullpath = getFullCommonPath(path);
if (!fullpath.empty() && unmountFullPath(fullpath.c_str()))
{
commonPathMountInfo[path].mounted = false;
return true;
}
return false;
}
bool Filesystem::unmount(Data *data)
{
for (const auto &datapair : mountedData)
@@ -503,9 +506,169 @@ bool Filesystem::unmount(Data *data)
return false;
}
love::filesystem::File *Filesystem::newFile(const char *filename) const
love::filesystem::File *Filesystem::openFile(const char *filename, File::Mode mode) const
{
return new File(filename);
return new File(filename, mode);
}
std::string Filesystem::getFullCommonPath(CommonPath path)
{
if (!fullPaths[path].empty())
return fullPaths[path];
if (isAppCommonPath(path))
{
if (saveIdentity.empty())
return fullPaths[path];
std::string rootpath;
switch (path)
{
case COMMONPATH_APP_SAVEDIR:
rootpath = getFullCommonPath(COMMONPATH_USER_APPDATA);
break;
case COMMONPATH_APP_DOCUMENTS:
rootpath = getFullCommonPath(COMMONPATH_USER_DOCUMENTS);
break;
default:
break;
}
if (rootpath.empty())
return fullPaths[path];
std::string suffix;
if (isFused())
suffix = std::string(LOVE_PATH_SEPARATOR) + saveIdentity;
else
suffix = std::string(LOVE_PATH_SEPARATOR LOVE_APPDATA_FOLDER LOVE_PATH_SEPARATOR) + saveIdentity;
fullPaths[path] = normalize(rootpath + suffix);
return fullPaths[path];
}
#if defined(LOVE_MACOS) || defined(LOVE_IOS)
switch (path)
{
case COMMONPATH_APP_SAVEDIR:
case COMMONPATH_APP_DOCUMENTS:
// Handled above.
break;
case COMMONPATH_USER_HOME:
fullPaths[path] = apple::getUserDirectory(apple::USER_DIRECTORY_HOME);
break;
case COMMONPATH_USER_APPDATA:
fullPaths[path] = apple::getUserDirectory(apple::USER_DIRECTORY_APPSUPPORT);
break;
case COMMONPATH_USER_DESKTOP:
fullPaths[path] = apple::getUserDirectory(apple::USER_DIRECTORY_DESKTOP);
break;
case COMMONPATH_USER_DOCUMENTS:
fullPaths[path] = apple::getUserDirectory(apple::USER_DIRECTORY_DOCUMENTS);
break;
case COMMONPATH_MAX_ENUM:
break;
}
#elif defined(LOVE_WINDOWS)
PWSTR winpath = nullptr;
HRESULT hr = E_FAIL;
switch (path)
{
case COMMONPATH_APP_SAVEDIR:
case COMMONPATH_APP_DOCUMENTS:
// Handled above.
break;
case COMMONPATH_USER_HOME:
hr = SHGetKnownFolderPath(FOLDERID_Profile, 0, nullptr, &winpath);
break;
case COMMONPATH_USER_APPDATA:
hr = SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, nullptr, &winpath);
break;
case COMMONPATH_USER_DESKTOP:
hr = SHGetKnownFolderPath(FOLDERID_Desktop, 0, nullptr, &winpath);
break;
case COMMONPATH_USER_DOCUMENTS:
hr = SHGetKnownFolderPath(FOLDERID_Documents, 0, nullptr, &winpath);
break;
case COMMONPATH_MAX_ENUM:
break;
}
if (SUCCEEDED(hr))
{
fullPaths[path] = to_utf8(winpath);
CoTaskMemFree(winpath);
}
#elif defined(LOVE_ANDROID)
std::string storagepath;
if (isAndroidSaveExternal())
storagepath = SDL_AndroidGetExternalStoragePath();
else
storagepath = SDL_AndroidGetInternalStoragePath();
switch (path)
{
case COMMONPATH_APP_SAVEDIR:
case COMMONPATH_APP_DOCUMENTS:
// Handled above.
break;
case COMMONPATH_USER_HOME:
fullPaths[path] = normalize(PHYSFS_getUserDir());
break;
case COMMONPATH_USER_APPDATA:
fullPaths[path] = normalize(storagepath + "/save/");
break;
case COMMONPATH_USER_DESKTOP:
// No such thing on Android?
break;
case COMMONPATH_USER_DOCUMENTS:
// TODO: something more idiomatic / useful?
fullPaths[path] = normalize(storagepath + "/Documents/");
break;
case COMMONPATH_MAX_ENUM:
break;
}
#elif defined(LOVE_LINUX)
const char *xdgdir = nullptr;
switch (path)
{
case COMMONPATH_APP_SAVEDIR:
case COMMONPATH_APP_DOCUMENTS:
// Handled above.
break;
case COMMONPATH_USER_HOME:
fullPaths[path] = normalize(PHYSFS_getUserDir());
break;
case COMMONPATH_USER_APPDATA:
xdgdir = getenv("XDG_DATA_HOME");
if (!xdgdir)
fullPaths[path] = normalize(std::string(getUserDirectory()) + "/.local/share/");
else
fullPaths[path] = xdgdir;
break;
case COMMONPATH_USER_DESKTOP:
fullPaths[path] = normalize(std::string(getUserDirectory()) + "/Desktop/");
break;
case COMMONPATH_USER_DOCUMENTS:
fullPaths[path] = normalize(std::string(getUserDirectory()) + "/Documents/");
break;
case COMMONPATH_MAX_ENUM:
break;
}
#endif
return fullPaths[path];
}
const char *Filesystem::getWorkingDirectory()
@@ -524,7 +687,7 @@ const char *Filesystem::getWorkingDirectory()
if (getcwd(cwd_char, LOVE_MAX_PATH))
cwd = cwd_char; // if getcwd fails, cwd_char (and thus cwd) will still be empty
delete [] cwd_char;
delete[] cwd_char;
#endif
}
@@ -533,54 +696,22 @@ const char *Filesystem::getWorkingDirectory()
std::string Filesystem::getUserDirectory()
{
#ifdef LOVE_IOS
// PHYSFS_getUserDir doesn't give exactly the path we want on iOS.
static std::string userDir = normalize(love::ios::getHomeDirectory());
#else
static std::string userDir = normalize(PHYSFS_getUserDir());
#endif
return userDir;
return getFullCommonPath(COMMONPATH_USER_HOME);
}
std::string Filesystem::getAppdataDirectory()
{
if (appdata.empty())
{
#ifdef LOVE_WINDOWS_UWP
appdata = getUserDirectory();
#elif defined(LOVE_WINDOWS)
wchar_t *w_appdata = _wgetenv(L"APPDATA");
appdata = to_utf8(w_appdata);
replace_char(appdata, '\\', '/');
#elif defined(LOVE_MACOSX)
std::string udir = getUserDirectory();
udir.append("/Library/Application Support");
appdata = normalize(udir);
#elif defined(LOVE_IOS)
appdata = normalize(love::ios::getAppdataDirectory());
#elif defined(LOVE_LINUX)
char *xdgdatahome = getenv("XDG_DATA_HOME");
if (!xdgdatahome)
appdata = normalize(std::string(getUserDirectory()) + "/.local/share/");
else
appdata = xdgdatahome;
#else
appdata = getUserDirectory();
#endif
}
return appdata;
return getFullCommonPath(COMMONPATH_USER_APPDATA);
}
const char *Filesystem::getSaveDirectory()
std::string Filesystem::getSaveDirectory()
{
return save_path_full.c_str();
return getFullCommonPath(COMMONPATH_APP_SAVEDIR);
}
std::string Filesystem::getSourceBaseDirectory() const
{
size_t source_len = game_source.length();
size_t source_len = gameSource.length();
if (source_len == 0)
return "";
@@ -589,9 +720,9 @@ std::string Filesystem::getSourceBaseDirectory() const
// symbols (i.e. '..' and '.')
#ifdef LOVE_WINDOWS
// In windows, delimiters can be either '/' or '\'.
size_t base_end_pos = game_source.find_last_of("/\\", source_len - 2);
size_t base_end_pos = gameSource.find_last_of("/\\", source_len - 2);
#else
size_t base_end_pos = game_source.find_last_of('/', source_len - 2);
size_t base_end_pos = gameSource.find_last_of('/', source_len - 2);
#endif
if (base_end_pos == std::string::npos)
@@ -601,7 +732,7 @@ std::string Filesystem::getSourceBaseDirectory() const
if (base_end_pos == 0)
base_end_pos = 1;
return game_source.substr(0, base_end_pos);
return gameSource.substr(0, base_end_pos);
}
std::string Filesystem::getRealDirectory(const char *filename) const
@@ -617,6 +748,14 @@ std::string Filesystem::getRealDirectory(const char *filename) const
return std::string(dir);
}
bool Filesystem::exists(const char *filepath) const
{
if (!PHYSFS_isInit())
return false;
return PHYSFS_exists(filepath) != 0;
}
bool Filesystem::getInfo(const char *filepath, Info &info) const
{
if (!PHYSFS_isInit())
@@ -628,6 +767,7 @@ bool Filesystem::getInfo(const char *filepath, Info &info) const
info.size = (int64) stat.filesize;
info.modtime = (int64) stat.modtime;
info.readonly = stat.readonly != 0;
if (stat.filetype == PHYSFS_FILETYPE_REGULAR)
info.type = FILETYPE_FILE;
@@ -646,7 +786,7 @@ bool Filesystem::createDirectory(const char *dir)
if (!PHYSFS_isInit())
return false;
if (PHYSFS_getWriteDir() == 0 && !setupWriteDirectory())
if (!setupWriteDirectory())
return false;
if (!PHYSFS_mkdir(dir))
@@ -660,7 +800,7 @@ bool Filesystem::remove(const char *file)
if (!PHYSFS_isInit())
return false;
if (PHYSFS_getWriteDir() == 0 && !setupWriteDirectory())
if (!setupWriteDirectory())
return false;
if (!PHYSFS_delete(file))
@@ -671,19 +811,23 @@ bool Filesystem::remove(const char *file)
FileData *Filesystem::read(const char *filename, int64 size) const
{
File file(filename);
file.open(File::MODE_READ);
File file(filename, File::MODE_READ);
// close() is called in the File destructor.
return file.read(size);
}
FileData* Filesystem::read(const char* filename) const
{
File file(filename, File::MODE_READ);
// close() is called in the File destructor.
return file.read();
}
void Filesystem::write(const char *filename, const void *data, int64 size) const
{
File file(filename);
file.open(File::MODE_WRITE);
File file(filename, File::MODE_WRITE);
// close() is called in the File destructor.
if (!file.write(data, size))
@@ -692,29 +836,28 @@ void Filesystem::write(const char *filename, const void *data, int64 size) const
void Filesystem::append(const char *filename, const void *data, int64 size) const
{
File file(filename);
file.open(File::MODE_APPEND);
File file(filename, File::MODE_APPEND);
// close() is called in the File destructor.
if (!file.write(data, size))
throw love::Exception("Data could not be written.");
}
void Filesystem::getDirectoryItems(const char *dir, std::vector<std::string> &items)
bool Filesystem::getDirectoryItems(const char *dir, std::vector<std::string> &items)
{
if (!PHYSFS_isInit())
return;
return false;
char **rc = PHYSFS_enumerateFiles(dir);
if (rc == nullptr)
return;
return false;
for (char **i = rc; *i != 0; i++)
items.push_back(*i);
PHYSFS_freeList(rc);
return true;
}
void Filesystem::setSymlinksEnabled(bool enable)
+32 -18
View File
@@ -63,30 +63,38 @@ public:
bool mount(const char *archive, const char *mountpoint, bool appendToPath = false) override;
bool mount(Data *data, const char *archivename, const char *mountpoint, bool appendToPath = false) override;
bool mountFullPath(const char *archive, const char *mountpoint, MountPermissions permissions, bool appendToPath = false) override;
bool mountCommonPath(CommonPath path, const char *mountpoint, MountPermissions permissions, bool appendToPath = false) override;
bool unmount(const char *archive) override;
bool unmount(Data *data) override;
bool unmount(CommonPath path) override;
bool unmountFullPath(const char *fullpath) override;
love::filesystem::File *newFile(const char *filename) const override;
love::filesystem::File *openFile(const char *filename, File::Mode mode) const override;
std::string getFullCommonPath(CommonPath path) override;
const char *getWorkingDirectory() override;
std::string getUserDirectory() override;
std::string getAppdataDirectory() override;
const char *getSaveDirectory() override;
std::string getSaveDirectory() override;
std::string getSourceBaseDirectory() const override;
std::string getRealDirectory(const char *filename) const override;
bool exists(const char *filepath) const override;
bool getInfo(const char *filepath, Info &info) const override;
bool createDirectory(const char *dir) override;
bool remove(const char *file) override;
FileData *read(const char *filename, int64 size = File::ALL) const override;
FileData *read(const char *filename, int64 size) const override;
FileData *read(const char *filename) const override;
void write(const char *filename, const void *data, int64 size) const override;
void append(const char *filename, const void *data, int64 size) const override;
void getDirectoryItems(const char *dir, std::vector<std::string> &items) override;
bool getDirectoryItems(const char *dir, std::vector<std::string> &items) override;
void setSymlinksEnabled(bool enable) override;
bool areSymlinksEnabled() const override;
@@ -98,26 +106,26 @@ public:
private:
struct CommonPathMountInfo
{
bool mounted;
std::string mountPoint;
MountPermissions permissions;
};
bool mountCommonPathInternal(CommonPath path, const char *mountpoint, MountPermissions permissions, bool appendToPath, bool createDir);
// Contains the current working directory (UTF8).
std::string cwd;
// %APPDATA% on Windows.
std::string appdata;
// This name will be used to create the folder
// in the appdata/userdata folder.
std::string save_identity;
// Full and relative paths of the game save folder.
// (Relative to the %APPDATA% folder, meaning that the
// relative string will look something like: ./LOVE/game)
std::string save_path_relative, save_path_full;
// This name will be used to create the folder in the appdata folder.
std::string saveIdentity;
bool appendIdentityToPath;
// The full path to the source of the game.
std::string game_source;
std::string gameSource;
// Allow saving outside of the LOVE_APPDATA_FOLDER
// for release 'builds'
// Allow saving outside of the LOVE_APPDATA_FOLDER for release 'builds'
bool fused;
bool fusedSet;
@@ -129,6 +137,12 @@ private:
std::map<std::string, StrongRef<Data>> mountedData;
std::string fullPaths[COMMONPATH_MAX_ENUM];
CommonPathMountInfo commonPathMountInfo[COMMONPATH_MAX_ENUM];
bool saveDirectoryNeedsMounting;
}; // Filesystem
} // physfs
+4 -2
View File
@@ -121,10 +121,12 @@ int w_File_read(lua_State *L)
startidx = 3;
}
int64 size = (int64) luaL_optnumber(L, startidx, (lua_Number) File::ALL);
int64 size = (int64) luaL_optnumber(L, startidx, -1);
try
{
if (size < 0)
size = file->getSize();
d.set(file->read(size), Acquire::NORETAIN);
}
catch (love::Exception &e)
@@ -169,7 +171,7 @@ int w_File_write(lua_State *L)
try
{
love::Data *data = luax_totype<love::Data>(L, 2);
result = file->write(data, luaL_optinteger(L, 3, data->getSize()));
result = file->write(data->getData(), luaL_optinteger(L, 3, data->getSize()));
}
catch (love::Exception &e)
{
+217 -163
View File
@@ -22,7 +22,7 @@
#include "common/config.h"
#include "wrap_Filesystem.h"
#include "wrap_File.h"
#include "wrap_DroppedFile.h"
#include "wrap_NativeFile.h"
#include "wrap_FileData.h"
#include "data/wrap_Data.h"
#include "data/wrap_DataModule.h"
@@ -49,13 +49,6 @@ namespace filesystem
#define instance() (Module::getInstance<Filesystem>(Module::M_FILESYSTEM))
bool hack_setupWriteDirectory()
{
if (instance() != 0)
return instance()->setupWriteDirectory();
return false;
}
int w_init(lua_State *L)
{
const char *arg0 = luaL_checkstring(L, 1);
@@ -144,9 +137,9 @@ int w_mount(lua_State *L)
luax_pushboolean(L, instance()->mount(data, archive.c_str(), mountpoint, append));
return 1;
}
else if (luax_istype(L, 1, DroppedFile::type))
else if (luax_istype(L, 1, NativeFile::type))
{
DroppedFile *file = luax_totype<DroppedFile>(L, 1);
NativeFile *file = luax_totype<NativeFile>(L, 1);
archive = file->getFilename();
}
else
@@ -159,6 +152,48 @@ int w_mount(lua_State *L)
return 1;
}
int w_mountFullPath(lua_State *L)
{
const char *fullpath = luaL_checkstring(L, 1);
const char *mountpoint = luaL_checkstring(L, 2);
auto permissions = Filesystem::MOUNT_PERMISSIONS_READ;
if (!lua_isnoneornil(L, 3))
{
const char *permissionstr = luaL_checkstring(L, 3);
if (!Filesystem::getConstant(permissionstr, permissions))
return luax_enumerror(L, "mount permissions", Filesystem::getConstants(permissions), permissionstr);
}
bool append = luax_optboolean(L, 4, false);
luax_pushboolean(L, instance()->mountFullPath(fullpath, mountpoint, permissions, append));
return 1;
}
int w_mountCommonPath(lua_State *L)
{
const char *commonpathstr = luaL_checkstring(L, 1);
Filesystem::CommonPath commonpath;
if (!Filesystem::getConstant(commonpathstr, commonpath))
return luax_enumerror(L, "common path", Filesystem::getConstants(commonpath), commonpathstr);
const char *mountpoint = luaL_checkstring(L, 2);
auto permissions = Filesystem::MOUNT_PERMISSIONS_READ;
if (!lua_isnoneornil(L, 3))
{
const char *permissionstr = luaL_checkstring(L, 3);
if (!Filesystem::getConstant(permissionstr, permissions))
return luax_enumerror(L, "mount permissions", Filesystem::getConstants(permissions), permissionstr);
}
bool append = luax_optboolean(L, 4, false);
luax_pushboolean(L, instance()->mountCommonPath(commonpath, mountpoint, permissions, append));
return 1;
}
int w_unmount(lua_State *L)
{
if (luax_istype(L, 1, Data::type))
@@ -174,34 +209,71 @@ int w_unmount(lua_State *L)
return 1;
}
int w_newFile(lua_State *L)
int w_unmountFullPath(lua_State *L)
{
const char *fullpath = luaL_checkstring(L, 1);
luax_pushboolean(L, instance()->unmountFullPath(fullpath));
return 1;
}
int w_unmountCommonPath(lua_State *L)
{
const char *commonpathstr = luaL_checkstring(L, 1);
Filesystem::CommonPath commonpath;
if (!Filesystem::getConstant(commonpathstr, commonpath))
return luax_enumerror(L, "common path", Filesystem::getConstants(commonpath), commonpathstr);
luax_pushboolean(L, instance()->unmount(commonpath));
return 1;
}
int w_openFile(lua_State *L)
{
const char *filename = luaL_checkstring(L, 1);
const char *modestr = luaL_checkstring(L, 2);
const char *str = 0;
File::Mode mode = File::MODE_CLOSED;
if (!File::getConstant(modestr, mode))
return luax_enumerror(L, "file open mode", File::getConstants(mode), modestr);
if (lua_isstring(L, 2))
File *t = nullptr;
try
{
str = luaL_checkstring(L, 2);
if (!File::getConstant(str, mode))
return luax_enumerror(L, "file open mode", File::getConstants(mode), str);
t = instance()->openFile(filename, mode);
}
catch (love::Exception &e)
{
return luax_ioError(L, "%s", e.what());
}
File *t = instance()->newFile(filename);
luax_pushtype(L, t);
t->release();
return 1;
}
if (mode != File::MODE_CLOSED)
int w_newFile(lua_State* L)
{
luax_markdeprecated(L, 1, "love.filesystem.newFile", API_FUNCTION, DEPRECATED_RENAMED, "love.filesystem.openFile");
const char* filename = luaL_checkstring(L, 1);
File::Mode mode = File::MODE_CLOSED;
if (!lua_isnoneornil(L, 2))
{
try
{
if (!t->open(mode))
throw love::Exception("Could not open file.");
}
catch (love::Exception &e)
{
t->release();
return luax_ioError(L, "%s", e.what());
}
const char* modestr = luaL_checkstring(L, 2);
if (!File::getConstant(modestr, mode))
return luax_enumerror(L, "file open mode", File::getConstants(mode), modestr);
}
File* t = nullptr;
try
{
t = instance()->openFile(filename, mode);
}
catch (love::Exception& e)
{
return luax_ioError(L, "%s", e.what());
}
luax_pushtype(L, t);
@@ -215,7 +287,14 @@ File *luax_getfile(lua_State *L, int idx)
if (lua_isstring(L, idx))
{
const char *filename = luaL_checkstring(L, idx);
file = instance()->newFile(filename);
try
{
file = instance()->openFile(filename, File::MODE_CLOSED);
}
catch (love::Exception &e)
{
luax_ioError(L, "%s", e.what());
}
}
else
{
@@ -226,10 +305,11 @@ 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, int &nresults)
{
FileData *data = nullptr;
File *file = nullptr;
nresults = 0;
if (lua_isstring(L, idx) || luax_istype(L, idx, File::type))
{
@@ -243,21 +323,37 @@ FileData *luax_getfiledata(lua_State *L, int idx)
if (!data && !file)
{
luaL_argerror(L, idx, "filename, File, or FileData expected");
nresults = 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)
nresults = luax_ioError(L, "%s", e.what());
else
nresults = luaL_error(L, "%s", e.what());
return nullptr; // Never reached if ioerror is false.
}
file->release();
}
return data;
}
FileData *luax_getfiledata(lua_State *L, int idx)
{
int nresults = 0;
return luax_getfiledata(L, idx, false, nresults);
}
Data *luax_getdata(lua_State *L, int idx)
{
Data *data = nullptr;
@@ -290,6 +386,11 @@ Data *luax_getdata(lua_State *L, int idx)
return data;
}
bool luax_cangetfile(lua_State *L, int idx)
{
return lua_isstring(L, idx) || luax_istype(L, idx, File::type);
}
bool luax_cangetfiledata(lua_State *L, int idx)
{
return lua_isstring(L, idx) || luax_istype(L, idx, File::type) || luax_istype(L, idx, FileData::type);
@@ -305,29 +406,13 @@ 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");
int nresults = 0;
FileData *data = luax_getfiledata(L, 1, true, nresults);
if (data == nullptr)
return nresults;
luax_pushtype(L, data);
data->release();
return 1;
}
size_t length = 0;
@@ -353,6 +438,17 @@ int w_newFileData(lua_State *L)
return 1;
}
int w_getFullCommonPath(lua_State *L)
{
const char *commonpathstr = luaL_checkstring(L, 1);
Filesystem::CommonPath commonpath;
if (!Filesystem::getConstant(commonpathstr, commonpath))
return luax_enumerror(L, "common path", Filesystem::getConstants(commonpath), commonpathstr);
luax_pushstring(L, instance()->getFullCommonPath(commonpath));
return 1;
}
int w_getWorkingDirectory(lua_State *L)
{
lua_pushstring(L, instance()->getWorkingDirectory());
@@ -373,7 +469,7 @@ int w_getAppdataDirectory(lua_State *L)
int w_getSaveDirectory(lua_State *L)
{
lua_pushstring(L, instance()->getSaveDirectory());
luax_pushstring(L, instance()->getSaveDirectory());
return 1;
}
@@ -407,6 +503,13 @@ int w_getExecutablePath(lua_State *L)
return 1;
}
int w_exists(lua_State *L)
{
const char *path = luaL_checkstring(L, 1);
luax_pushboolean(L, instance()->exists(path));
return 1;
}
int w_getInfo(lua_State *L)
{
const char *filepath = luaL_checkstring(L, 1);
@@ -443,6 +546,9 @@ int w_getInfo(lua_State *L)
lua_pushstring(L, typestr);
lua_setfield(L, -2, "type");
luax_pushboolean(L, info.readonly);
lua_setfield(L, -2, "readonly");
// Lua numbers (doubles) can't fit the full range of 64 bit ints.
info.size = std::min<int64>(info.size, 0x20000000000000LL);
if (info.size >= 0)
@@ -490,12 +596,15 @@ int w_read(lua_State *L)
}
const char *filename = luaL_checkstring(L, startidx + 0);
int64 len = (int64) luaL_optinteger(L, startidx + 1, File::ALL);
int64 len = (int64) luaL_optinteger(L, startidx + 1, -1);
FileData *data = nullptr;
try
{
data = instance()->read(filename, len);
if (len >= 0)
data = instance()->read(filename, len);
else
data = instance()->read(filename);
}
catch (love::Exception &e)
{
@@ -588,16 +697,8 @@ int w_lines(lua_State *L)
{
if (lua_isstring(L, 1))
{
File *file = instance()->newFile(lua_tostring(L, 1));
bool success = false;
luax_catchexcept(L, [&](){ success = file->open(File::MODE_READ); });
if (!success)
{
file->release();
return luaL_error(L, "Could not open file.");
}
File *file = nullptr;
luax_catchexcept(L, [&]() { file = instance()->openFile(lua_tostring(L, 1), File::MODE_READ); });
luax_pushtype(L, file);
file->release();
@@ -615,6 +716,16 @@ int w_load(lua_State *L)
{
std::string filename = std::string(luaL_checkstring(L, 1));
Filesystem::LoadMode loadMode = Filesystem::LOADMODE_ANY;
if (!lua_isnoneornil(L, 2))
{
const char *mode = luaL_checkstring(L, 2);
if (!Filesystem::getConstant(mode, loadMode))
return luax_enumerror(L, "load mode", Filesystem::getConstants(loadMode), mode);
}
Data *data = nullptr;
try
{
@@ -625,7 +736,24 @@ int w_load(lua_State *L)
return luax_ioError(L, "%s", e.what());
}
int status = luaL_loadbuffer(L, (const char *)data->getData(), data->getSize(), ("@" + filename).c_str());
int status;
#if (LUA_VERSION_NUM > 501) || defined(LUA_JITLIBNAME)
// LuaJIT support this Lua 5.2 function.
const char *mode;
Filesystem::getConstant(loadMode, mode);
status = luaL_loadbufferx(L, (const char *)data->getData(), data->getSize(), ("@" + filename).c_str(), mode);
#else
if (loadMode == Filesystem::LOADMODE_ANY)
status = luaL_loadbuffer(L, (const char *)data->getData(), data->getSize(), ("@" + filename).c_str());
else
{
// Unsupported
data->release();
return luaL_error(L, "only \"bt\" is supported on this Lua interpreter\n");
}
#endif
data->release();
@@ -738,6 +866,7 @@ static void replaceAll(std::string &str, const std::string &substr, const std::s
int loader(lua_State *L)
{
std::string modulename = luax_checkstring(L, 1);
bool hasSlash = modulename.find('/') != std::string::npos;
for (char &c : modulename)
{
@@ -753,6 +882,9 @@ int loader(lua_State *L)
Filesystem::Info info = {};
if (inst->getInfo(element.c_str(), info) && info.type != Filesystem::FILETYPE_DIRECTORY)
{
if (hasSlash)
luax_markdeprecated(L, 2, "character in require string (forward slashes), use dots instead.", API_CUSTOM);
lua_pop(L, 1);
lua_pushstring(L, element.c_str());
return w_load(L);
@@ -769,7 +901,7 @@ static const char *library_extensions[] =
{
#ifdef LOVE_WINDOWS
".dll"
#elif defined(LOVE_MACOSX) || defined(LOVE_IOS)
#elif defined(LOVE_MACOS) || defined(LOVE_IOS)
".dylib", ".so"
#else
".so"
@@ -870,85 +1002,6 @@ int extloader(lua_State *L)
return 1;
}
// Deprecated functions.
int w_exists(lua_State *L)
{
luax_markdeprecated(L, "love.filesystem.exists", API_FUNCTION, DEPRECATED_REPLACED, "love.filesystem.getInfo");
const char *arg = luaL_checkstring(L, 1);
Filesystem::Info info = {};
luax_pushboolean(L, instance()->getInfo(arg, info));
return 1;
}
int w_isDirectory(lua_State *L)
{
luax_markdeprecated(L, "love.filesystem.isDirectory", API_FUNCTION, DEPRECATED_REPLACED, "love.filesystem.getInfo");
const char *arg = luaL_checkstring(L, 1);
Filesystem::Info info = {};
bool exists = instance()->getInfo(arg, info);
luax_pushboolean(L, exists && info.type == Filesystem::FILETYPE_DIRECTORY);
return 1;
}
int w_isFile(lua_State *L)
{
luax_markdeprecated(L, "love.filesystem.isFile", API_FUNCTION, DEPRECATED_REPLACED, "love.filesystem.getInfo");
const char *arg = luaL_checkstring(L, 1);
Filesystem::Info info = {};
bool exists = instance()->getInfo(arg, info);
luax_pushboolean(L, exists && info.type == Filesystem::FILETYPE_FILE);
return 1;
}
int w_isSymlink(lua_State *L)
{
luax_markdeprecated(L, "love.filesystem.isSymlink", API_FUNCTION, DEPRECATED_REPLACED, "love.filesystem.getInfo");
const char *filename = luaL_checkstring(L, 1);
Filesystem::Info info = {};
bool exists = instance()->getInfo(filename, info);
luax_pushboolean(L, exists && info.type == Filesystem::FILETYPE_SYMLINK);
return 1;
}
int w_getLastModified(lua_State *L)
{
luax_markdeprecated(L, "love.filesystem.getLastModified", API_FUNCTION, DEPRECATED_REPLACED, "love.filesystem.getInfo");
const char *filename = luaL_checkstring(L, 1);
Filesystem::Info info = {};
bool exists = instance()->getInfo(filename, info);
if (!exists)
return luax_ioError(L, "File does not exist");
else if (info.modtime == -1)
return luax_ioError(L, "Could not determine file modification date.");
lua_pushnumber(L, (lua_Number) info.modtime);
return 1;
}
int w_getSize(lua_State *L)
{
luax_markdeprecated(L, "love.filesystem.getSize", API_FUNCTION, DEPRECATED_REPLACED, "love.filesystem.getInfo");
const char *filename = luaL_checkstring(L, 1);
Filesystem::Info info = {};
bool exists = instance()->getInfo(filename, info);
if (!exists)
luax_ioError(L, "File does not exist");
else if (info.size == -1)
return luax_ioError(L, "Could not determine file size.");
else if (info.size >= 0x20000000000000LL)
return luax_ioError(L, "Size too large to fit into a Lua number!");
lua_pushnumber(L, (lua_Number) info.size);
return 1;
}
// List of functions to wrap.
static const luaL_Reg functions[] =
{
@@ -961,8 +1014,13 @@ static const luaL_Reg functions[] =
{ "setSource", w_setSource },
{ "getSource", w_getSource },
{ "mount", w_mount },
{ "mountFullPath", w_mountFullPath },
{ "mountCommonPath", w_mountCommonPath },
{ "unmount", w_unmount },
{ "newFile", w_newFile },
{ "unmountFullPath", w_unmountFullPath },
{ "unmountCommonPath", w_unmountCommonPath },
{ "openFile", w_openFile },
{ "getFullCommonPath", w_getFullCommonPath },
{ "getWorkingDirectory", w_getWorkingDirectory },
{ "getUserDirectory", w_getUserDirectory },
{ "getAppdataDirectory", w_getAppdataDirectory },
@@ -978,6 +1036,7 @@ static const luaL_Reg functions[] =
{ "getDirectoryItems", w_getDirectoryItems },
{ "lines", w_lines },
{ "load", w_load },
{ "exists", w_exists },
{ "getInfo", w_getInfo },
{ "setSymlinksEnabled", w_setSymlinksEnabled },
{ "areSymlinksEnabled", w_areSymlinksEnabled },
@@ -987,13 +1046,8 @@ static const luaL_Reg functions[] =
{ "getCRequirePath", w_getCRequirePath },
{ "setCRequirePath", w_setCRequirePath },
// Deprecated.
{ "exists", w_exists },
{ "isDirectory", w_isDirectory },
{ "isFile", w_isFile },
{ "isSymlink", w_isSymlink },
{ "getLastModified", w_getLastModified },
{ "getSize", w_getSize },
// Deprecated
{ "newFile", w_newFile },
{ 0, 0 }
};
@@ -1001,7 +1055,7 @@ static const luaL_Reg functions[] =
static const lua_CFunction types[] =
{
luaopen_file,
luaopen_droppedfile,
luaopen_nativefile,
luaopen_filedata,
0
};
+2 -1
View File
@@ -40,12 +40,13 @@ namespace filesystem
**/
FileData *luax_getfiledata(lua_State *L, int idx);
bool luax_cangetfiledata(lua_State *L, int idx);
File *luax_getfile(lua_State *L, int idx);
bool luax_cangetfile(lua_State *L, int idx);
Data *luax_getdata(lua_State *L, int idx);
bool luax_cangetdata(lua_State *L, int idx);
bool hack_setupWriteDirectory();
int loader(lua_State *L);
int extloader(lua_State *L);
extern "C" LOVE_EXPORT int luaopen_love_filesystem(lua_State *L);
@@ -18,7 +18,7 @@
* 3. This notice may not be removed or altered from any source distribution.
**/
#include "wrap_DroppedFile.h"
#include "wrap_NativeFile.h"
#include "wrap_File.h"
namespace love
@@ -26,14 +26,14 @@ namespace love
namespace filesystem
{
DroppedFile *luax_checkdroppedfile(lua_State *L, int idx)
NativeFile *luax_checknativefile(lua_State *L, int idx)
{
return luax_checktype<DroppedFile>(L, idx);
return luax_checktype<NativeFile>(L, idx);
}
extern "C" int luaopen_droppedfile(lua_State *L)
extern "C" int luaopen_nativefile(lua_State *L)
{
return luax_register_type(L, &DroppedFile::type, w_File_functions, nullptr);
return luax_register_type(L, &NativeFile::type, w_File_functions, nullptr);
}
} // filesystem
@@ -22,17 +22,15 @@
// LOVE
#include "common/runtime.h"
#include "Canvas.h"
#include "wrap_Texture.h"
#include "NativeFile.h"
namespace love
{
namespace graphics
namespace filesystem
{
//see Canvas.h
Canvas *luax_checkcanvas(lua_State *L, int idx);
extern "C" int luaopen_canvas(lua_State *L);
NativeFile *luax_checknativefile(lua_State *L, int idx);
extern "C" int luaopen_nativefile(lua_State *L);
} // graphics
} // filesystem
} // love
+51 -17
View File
@@ -20,6 +20,7 @@
// LOVE
#include "BMFontRasterizer.h"
#include "GenericShaper.h"
#include "filesystem/Filesystem.h"
#include "image/Image.h"
@@ -147,7 +148,7 @@ BMFontRasterizer::BMFontRasterizer(love::filesystem::FileData *fontdef, const st
// The parseConfig function will try to load any missing page images.
for (int i = 0; i < (int) imagelist.size(); i++)
{
if (imagelist[i]->getFormat() != PIXELFORMAT_RGBA8)
if (imagelist[i]->getFormat() != PIXELFORMAT_RGBA8_UNORM)
throw love::Exception("Only 32-bit RGBA images are supported in BMFonts.");
images[i] = imagelist[i];
@@ -164,6 +165,14 @@ BMFontRasterizer::~BMFontRasterizer()
void BMFontRasterizer::parseConfig(const std::string &configtext)
{
{
BMFontCharacter nullchar = {};
nullchar.page = -1;
nullchar.glyph = 0;
characters.push_back(nullchar);
characterIndices[0] = (int)characters.size() - 1;
}
std::stringstream ss(configtext);
std::string line;
@@ -211,7 +220,7 @@ void BMFontRasterizer::parseConfig(const std::string &configtext)
ImageData *imagedata = imagemodule->newImageData(data.get());
if (imagedata->getFormat() != PIXELFORMAT_RGBA8)
if (imagedata->getFormat() != PIXELFORMAT_RGBA8_UNORM)
{
imagedata->release();
throw love::Exception("Only 32-bit RGBA images are supported in BMFonts.");
@@ -237,7 +246,10 @@ void BMFontRasterizer::parseConfig(const std::string &configtext)
c.metrics.bearingY = -cline.getAttributeInt("yoffset");
c.metrics.advance = cline.getAttributeInt("xadvance");
characters[id] = c;
c.glyph = id;
characters.push_back(c);
characterIndices[id] = (int) characters.size() - 1;
}
else if (tag == "kerning")
{
@@ -257,13 +269,15 @@ void BMFontRasterizer::parseConfig(const std::string &configtext)
bool guessheight = lineHeight == 0;
// Verify the glyph character attributes.
for (const auto &cpair : characters)
for (const auto &c : characters)
{
const BMFontCharacter &c = cpair.second;
if (c.glyph == 0)
continue;
int width = c.metrics.width;
int height = c.metrics.height;
if (!unicode && cpair.first > 127)
if (!unicode && c.glyph > 127)
throw love::Exception("Invalid BMFont character id (only unicode and ASCII are supported)");
if (c.page < 0 || images[c.page].get() == nullptr)
@@ -272,13 +286,13 @@ void BMFontRasterizer::parseConfig(const std::string &configtext)
const image::ImageData *id = images[c.page].get();
if (!id->inside(c.x, c.y))
throw love::Exception("Invalid coordinates for BMFont character %u.", cpair.first);
throw love::Exception("Invalid coordinates for BMFont character %u.", c.glyph);
if (width > 0 && !id->inside(c.x + width - 1, c.y))
throw love::Exception("Invalid width %d for BMFont character %u.", width, cpair.first);
throw love::Exception("Invalid width %d for BMFont character %u.", width, c.glyph);
if (height > 0 && !id->inside(c.x, c.y + height - 1))
throw love::Exception("Invalid height %d for BMFont character %u.", height, cpair.first);
throw love::Exception("Invalid height %d for BMFont character %u.", height, c.glyph);
if (guessheight)
lineHeight = std::max(lineHeight, c.metrics.height);
@@ -292,22 +306,37 @@ int BMFontRasterizer::getLineHeight() const
return lineHeight;
}
GlyphData *BMFontRasterizer::getGlyphData(uint32 glyph) const
int BMFontRasterizer::getGlyphSpacing(uint32 glyph) const
{
auto it = characters.find(glyph);
auto it = characterIndices.find(glyph);
if (it == characterIndices.end())
return 0;
return characters[it->second].metrics.advance;
}
int BMFontRasterizer::getGlyphIndex(uint32 glyph) const
{
auto it = characterIndices.find(glyph);
if (it == characterIndices.end())
return 0;
return it->second;
}
GlyphData *BMFontRasterizer::getGlyphDataForIndex(int index) const
{
// Return an empty GlyphData if we don't have the glyph character.
if (it == characters.end())
return new GlyphData(glyph, GlyphMetrics(), PIXELFORMAT_RGBA8);
if (index < 0 || index >= (int) characters.size())
return new GlyphData(0, GlyphMetrics(), PIXELFORMAT_RGBA8_UNORM);
const BMFontCharacter &c = it->second;
const BMFontCharacter& c = characters[index];
const auto &imagepair = images.find(c.page);
if (imagepair == images.end())
return new GlyphData(glyph, GlyphMetrics(), PIXELFORMAT_RGBA8);
return new GlyphData(c.glyph, GlyphMetrics(), PIXELFORMAT_RGBA8_UNORM);
image::ImageData *imagedata = imagepair->second.get();
GlyphData *g = new GlyphData(glyph, c.metrics, PIXELFORMAT_RGBA8);
GlyphData *g = new GlyphData(c.glyph, c.metrics, PIXELFORMAT_RGBA8_UNORM);
size_t pixelsize = imagedata->getPixelSize();
@@ -333,7 +362,7 @@ int BMFontRasterizer::getGlyphCount() const
bool BMFontRasterizer::hasGlyph(uint32 glyph) const
{
return characters.find(glyph) != characters.end();
return characterIndices.find(glyph) != characterIndices.end();
}
float BMFontRasterizer::getKerning(uint32 leftglyph, uint32 rightglyph) const
@@ -352,6 +381,11 @@ Rasterizer::DataType BMFontRasterizer::getDataType() const
return DATA_IMAGE;
}
TextShaper *BMFontRasterizer::newTextShaper()
{
return new GenericShaper(this);
}
bool BMFontRasterizer::accepts(love::filesystem::FileData *fontdef)
{
const char *data = (const char *) fontdef->getData();
+9 -3
View File
@@ -47,11 +47,14 @@ public:
// Implements Rasterizer.
int getLineHeight() const override;
GlyphData *getGlyphData(uint32 glyph) const override;
int getGlyphSpacing(uint32 glyph) const override;
int getGlyphIndex(uint32 glyph) const override;
GlyphData *getGlyphDataForIndex(int index) const override;
int getGlyphCount() const override;
bool hasGlyph(uint32 glyph) const override;
float getKerning(uint32 leftglyph, uint32 rightglyph) const override;
DataType getDataType() const override;
TextShaper *newTextShaper() override;
static bool accepts(love::filesystem::FileData *fontdef);
@@ -63,6 +66,7 @@ private:
int y;
int page;
GlyphMetrics metrics;
uint32 glyph;
};
void parseConfig(const std::string &config);
@@ -72,8 +76,10 @@ private:
// Image pages, indexed by their page id.
std::unordered_map<int, StrongRef<image::ImageData>> images;
// Glyph characters, indexed by their glyph id.
std::unordered_map<uint32, BMFontCharacter> characters;
std::vector<BMFontCharacter> characters;
// Glyph character indices, indexed by their glyph id.
std::unordered_map<uint32, int> characterIndices;
// Kerning information, indexed by two (packed) characters.
std::unordered_map<uint64, int> kerning;
+13 -12
View File
@@ -22,6 +22,7 @@
#include "Font.h"
#include "BMFontRasterizer.h"
#include "ImageRasterizer.h"
#include "data/DataModule.h"
#include "libraries/utf8/utf8.h"
@@ -30,28 +31,28 @@ namespace love
namespace font
{
// Default TrueType font.
#include "Vera.ttf.h"
// Default TrueType font, gzip-compressed.
#include "NotoSans-Regular.ttf.gzip.h"
class DefaultFontData : public love::Data
Font::Font()
{
public:
auto compressedbytes = (const char *) NotoSans_Regular_ttf_gzip;
size_t compressedsize = NotoSans_Regular_ttf_gzip_len;
Data *clone() const override { return new DefaultFontData(); }
void *getData() const override { return Vera_ttf; }
size_t getSize() const override { return sizeof(Vera_ttf); }
};
size_t rawsize = 0;
char *fontdata = data::decompress(data::Compressor::FORMAT_GZIP, compressedbytes, compressedsize, rawsize);
defaultFontData.set(new data::ByteData(fontdata, rawsize, true), Acquire::NORETAIN);
}
Rasterizer *Font::newTrueTypeRasterizer(int size, TrueTypeRasterizer::Hinting hinting)
{
StrongRef<DefaultFontData> data(new DefaultFontData, Acquire::NORETAIN);
return newTrueTypeRasterizer(data.get(), size, hinting);
return newTrueTypeRasterizer(defaultFontData.get(), size, hinting);
}
Rasterizer *Font::newTrueTypeRasterizer(int size, float dpiscale, TrueTypeRasterizer::Hinting hinting)
{
StrongRef<DefaultFontData> data(new DefaultFontData, Acquire::NORETAIN);
return newTrueTypeRasterizer(data.get(), size, dpiscale, hinting);
return newTrueTypeRasterizer(defaultFontData.get(), size, dpiscale, hinting);
}
Rasterizer *Font::newBMFontRasterizer(love::filesystem::FileData *fontdef, const std::vector<image::ImageData *> &images, float dpiscale)
+5
View File
@@ -43,6 +43,7 @@ class Font : public Module
public:
Font();
virtual ~Font() {}
virtual Rasterizer *newRasterizer(love::filesystem::FileData *data) = 0;
@@ -64,6 +65,10 @@ public:
virtual ModuleType getModuleType() const { return M_FONT; }
virtual const char *getName() const = 0;
private:
StrongRef<Data> defaultFontData;
}; // Font
} // font
+203
View File
@@ -0,0 +1,203 @@
/**
* Copyright (c) 2006-2023 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 "GenericShaper.h"
#include "Rasterizer.h"
#include "common/Optional.h"
namespace love
{
namespace font
{
GenericShaper::GenericShaper(Rasterizer *rasterizer)
: TextShaper(rasterizer)
{
}
GenericShaper::~GenericShaper()
{
}
void GenericShaper::computeGlyphPositions(const ColoredCodepoints &codepoints, Range range, Vector2 offset, float extraspacing, std::vector<GlyphPosition> *positions, std::vector<IndexedColor> *colors, TextInfo *info)
{
if (!range.isValid())
range = Range(0, codepoints.cps.size());
if (rasterizers[0]->getDataType() == Rasterizer::DATA_TRUETYPE)
offset.y += getBaseline();
// Spacing counter and newline handling.
Vector2 curpos = offset;
int maxwidth = 0;
uint32 prevglyph = 0;
if (positions)
positions->reserve(range.getSize());
int colorindex = 0;
int ncolors = (int) codepoints.colors.size();
Optional<Colorf> colorToAdd;
// Make sure the right color is applied to the start of the glyph list,
// when the start isn't 0.
if (colors && range.getOffset() > 0 && !codepoints.colors.empty())
{
for (; colorindex < ncolors; colorindex++)
{
if (codepoints.colors[colorindex].index >= (int) range.getOffset())
break;
colorToAdd.set(codepoints.colors[colorindex].color);
}
}
for (int i = (int) range.getMin(); i <= (int) range.getMax(); i++)
{
uint32 g = codepoints.cps[i];
// Do this before anything else so we don't miss colors corresponding
// to newlines. The actual add to the list happens after newline
// handling, to make sure the resulting index is valid in the positions
// array.
if (colors && colorindex < ncolors && codepoints.colors[colorindex].index == i)
{
colorToAdd.set(codepoints.colors[colorindex].color);
colorindex++;
}
if (g == '\n')
{
if (curpos.x > maxwidth)
maxwidth = (int)curpos.x;
// Wrap newline, but do not output a position for it.
curpos.y += floorf(getHeight() * getLineHeight() + 0.5f);
curpos.x = offset.x;
prevglyph = 0;
continue;
}
// Ignore carriage returns
if (g == '\r')
{
prevglyph = g;
continue;
}
if (colorToAdd.hasValue && colors && positions)
{
IndexedColor c = {colorToAdd.value, (int) positions->size()};
colors->push_back(c);
colorToAdd.clear();
}
// Add kerning to the current horizontal offset.
curpos.x += getKerning(prevglyph, g);
GlyphIndex glyphindex;
int advance = getGlyphAdvance(g, &glyphindex);
if (positions)
positions->push_back({ Vector2(curpos.x, curpos.y), glyphindex });
// Advance the x position for the next glyph.
curpos.x += advance;
// Account for extra spacing given to space characters.
if (g == ' ' && extraspacing != 0.0f)
curpos.x = floorf(curpos.x + extraspacing);
prevglyph = g;
}
if (curpos.x > maxwidth)
maxwidth = (int)curpos.x;
if (info != nullptr)
{
info->width = maxwidth - offset.x;
info->height = curpos.y - offset.y;
if (curpos.x > offset.x)
info->height += floorf(getHeight() * getLineHeight() + 0.5f);
}
}
int GenericShaper::computeWordWrapIndex(const ColoredCodepoints &codepoints, Range range, float wraplimit, float *width)
{
if (!range.isValid())
range = Range(0, codepoints.cps.size());
uint32 prevglyph = 0;
float w = 0.0f;
float outwidth = 0.0f;
float widthbeforelastspace = 0.0f;
int wrapindex = -1;
int lastspaceindex = -1;
for (int i = (int)range.getMin(); i <= (int)range.getMax(); i++)
{
uint32 g = codepoints.cps[i];
if (g == '\r')
{
prevglyph = g;
continue;
}
float newwidth = w + getKerning(prevglyph, g) + getGlyphAdvance(g);
// Only wrap when there's a non-space character.
if (newwidth > wraplimit && !isWhitespace(g))
{
// Rewind to the last seen space when wrapping.
if (lastspaceindex != -1)
{
wrapindex = lastspaceindex;
outwidth = widthbeforelastspace;
}
break;
}
// Don't count trailing spaces in the output width.
if (isWhitespace(g))
{
lastspaceindex = i;
if (!isWhitespace(prevglyph))
widthbeforelastspace = w;
}
else
outwidth = newwidth;
w = newwidth;
prevglyph = g;
wrapindex = i;
}
if (width)
*width = outwidth;
return wrapindex;
}
} // font
} // love
+46
View File
@@ -0,0 +1,46 @@
/**
* Copyright (c) 2006-2023 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 "TextShaper.h"
namespace love
{
namespace font
{
class GenericShaper : public love::font::TextShaper
{
public:
GenericShaper(Rasterizer *rasterizer);
virtual ~GenericShaper();
void computeGlyphPositions(const ColoredCodepoints &codepoints, Range range, Vector2 offset, float extraspacing, std::vector<GlyphPosition> *positions, std::vector<IndexedColor> *colors, TextInfo *info) override;
int computeWordWrapIndex(const ColoredCodepoints &codepoints, Range range, float wraplimit, float *width) override;
private:
}; // GenericShaper
} // font
} // love
+2 -6
View File
@@ -24,10 +24,6 @@
// UTF-8
#include "libraries/utf8/utf8.h"
// stdlib
#include <iostream>
#include <cstddef>
namespace love
{
namespace font
@@ -41,7 +37,7 @@ GlyphData::GlyphData(uint32 glyph, GlyphMetrics glyphMetrics, PixelFormat f)
, data(nullptr)
, format(f)
{
if (f != PIXELFORMAT_LA8 && f != PIXELFORMAT_RGBA8)
if (f != PIXELFORMAT_LA8_UNORM && f != PIXELFORMAT_RGBA8_UNORM)
throw love::Exception("Invalid GlyphData pixel format.");
if (metrics.width > 0 && metrics.height > 0)
@@ -78,7 +74,7 @@ void *GlyphData::getData() const
size_t GlyphData::getPixelSize() const
{
return getPixelFormatSize(format);
return getPixelFormatBlockSize(format);
}
void *GlyphData::getData(int x, int y) const
+50 -17
View File
@@ -20,8 +20,9 @@
// LOVE
#include "ImageRasterizer.h"
#include "GenericShaper.h"
#include "common/Exception.h"
#include <string.h>
namespace love
@@ -31,18 +32,17 @@ namespace font
static_assert(sizeof(Color32) == 4, "sizeof(Color32) must equal 4 bytes!");
ImageRasterizer::ImageRasterizer(love::image::ImageData *data, uint32 *glyphs, int numglyphs, int extraspacing, float dpiscale)
ImageRasterizer::ImageRasterizer(love::image::ImageData *data, const uint32 *glyphs, int numglyphs, int extraspacing, float dpiscale)
: imageData(data)
, glyphs(glyphs)
, numglyphs(numglyphs)
, numglyphs(numglyphs + 1) // Always have a null glyph at the start of the array.
, extraSpacing(extraspacing)
{
this->dpiScale = dpiscale;
if (data->getFormat() != PIXELFORMAT_RGBA8)
if (data->getFormat() != PIXELFORMAT_RGBA8_UNORM)
throw love::Exception("Only 32-bit RGBA images are supported in Image Fonts!");
load();
load(glyphs, numglyphs);
}
ImageRasterizer::~ImageRasterizer()
@@ -54,21 +54,38 @@ int ImageRasterizer::getLineHeight() const
return getHeight();
}
GlyphData *ImageRasterizer::getGlyphData(uint32 glyph) const
int ImageRasterizer::getGlyphSpacing(uint32 glyph) const
{
auto it = glyphIndices.find(glyph);
if (it == glyphIndices.end())
return 0;
return imageGlyphs[it->second].width + extraSpacing;
}
int ImageRasterizer::getGlyphIndex(uint32 glyph) const
{
auto it = glyphIndices.find(glyph);
if (it == glyphIndices.end())
return 0;
return it->second;
}
GlyphData *ImageRasterizer::getGlyphDataForIndex(int index) const
{
GlyphMetrics gm = {};
uint32 glyph = 0;
// Set relevant glyph metrics if the glyph is in this ImageFont
std::map<uint32, ImageGlyphData>::const_iterator it = imageGlyphs.find(glyph);
if (it != imageGlyphs.end())
if (index >= 0 && index < (int) imageGlyphs.size())
{
gm.width = it->second.width;
gm.advance = it->second.width + extraSpacing;
gm.width = imageGlyphs[index].width;
gm.advance = imageGlyphs[index].width + extraSpacing;
glyph = imageGlyphs[index].glyph;
}
gm.height = metrics.height;
GlyphData *g = new GlyphData(glyph, gm, PIXELFORMAT_RGBA8);
GlyphData *g = new GlyphData(glyph, gm, PIXELFORMAT_RGBA8_UNORM);
if (gm.width == 0)
return g;
@@ -82,7 +99,7 @@ GlyphData *ImageRasterizer::getGlyphData(uint32 glyph) const
// copy glyph pixels from imagedata to glyphdata
for (int i = 0; i < g->getWidth() * g->getHeight(); i++)
{
Color32 p = imagepixels[it->second.x + (i % gm.width) + (imageData->getWidth() * (i / gm.width))];
Color32 p = imagepixels[imageGlyphs[index].x + (i % gm.width) + (imageData->getWidth() * (i / gm.width))];
// Use transparency instead of the spacer color
if (p == spacer)
@@ -94,7 +111,7 @@ GlyphData *ImageRasterizer::getGlyphData(uint32 glyph) const
return g;
}
void ImageRasterizer::load()
void ImageRasterizer::load(const uint32 *glyphs, int glyphcount)
{
auto pixels = (const Color32 *) imageData->getData();
@@ -113,7 +130,16 @@ void ImageRasterizer::load()
int start = 0;
int end = 0;
for (int i = 0; i < numglyphs; ++i)
{
ImageGlyphData nullglyph;
nullglyph.x = 0;
nullglyph.width = 0;
nullglyph.glyph = 0;
imageGlyphs.push_back(nullglyph);
glyphIndices[0] = (int) imageGlyphs.size() - 1;
}
for (int i = 0; i < glyphcount; ++i)
{
start = end;
@@ -133,8 +159,10 @@ void ImageRasterizer::load()
ImageGlyphData imageGlyph;
imageGlyph.x = start;
imageGlyph.width = end - start;
imageGlyph.glyph = glyphs[i];
imageGlyphs[glyphs[i]] = imageGlyph;
imageGlyphs.push_back(imageGlyph);
glyphIndices[glyphs[i]] = (int) imageGlyphs.size() - 1;
}
}
@@ -145,7 +173,7 @@ int ImageRasterizer::getGlyphCount() const
bool ImageRasterizer::hasGlyph(uint32 glyph) const
{
return imageGlyphs.find(glyph) != imageGlyphs.end();
return glyphIndices.find(glyph) != glyphIndices.end();
}
Rasterizer::DataType ImageRasterizer::getDataType() const
@@ -153,5 +181,10 @@ Rasterizer::DataType ImageRasterizer::getDataType() const
return DATA_IMAGE;
}
TextShaper *ImageRasterizer::newTextShaper()
{
return new GenericShaper(this);
}
} // font
} // love
+10 -7
View File
@@ -39,15 +39,18 @@ namespace font
class ImageRasterizer : public Rasterizer
{
public:
ImageRasterizer(love::image::ImageData *imageData, uint32 *glyphs, int numglyphs, int extraspacing, float dpiscale);
ImageRasterizer(love::image::ImageData *imageData, const uint32 *glyphs, int numglyphs, int extraspacing, float dpiscale);
virtual ~ImageRasterizer();
// Implement Rasterizer
int getLineHeight() const override;
GlyphData *getGlyphData(uint32 glyph) const override;
int getGlyphSpacing(uint32 glyph) const override;
int getGlyphIndex(uint32 glyph) const override;
GlyphData *getGlyphDataForIndex(int index) const override;
int getGlyphCount() const override;
bool hasGlyph(uint32 glyph) const override;
DataType getDataType() const override;
TextShaper *newTextShaper() override;
private:
@@ -57,23 +60,23 @@ private:
{
int x;
int width;
uint32 glyph;
};
// Load all the glyph positions into memory
void load();
void load(const uint32 *glyphs, int glyphcount);
// The image data
StrongRef<love::image::ImageData> imageData;
// The glyphs in the font
uint32 *glyphs;
// Number of glyphs in the font
int numglyphs;
int extraSpacing;
std::map<uint32, ImageGlyphData> imageGlyphs;
std::vector<ImageGlyphData> imageGlyphs;
std::map<uint32, int> glyphIndices;
// Color used to identify glyph separation in the source ImageData
Color32 spacer;
File diff suppressed because it is too large Load Diff
+5
View File
@@ -55,6 +55,11 @@ int Rasterizer::getDescent() const
return metrics.descent;
}
GlyphData *Rasterizer::getGlyphData(uint32 glyph) const
{
return getGlyphDataForIndex(getGlyphIndex(glyph));
}
GlyphData *Rasterizer::getGlyphData(const std::string &text) const
{
uint32 codepoint = 0;
+23 -2
View File
@@ -31,6 +31,8 @@ namespace love
namespace font
{
class TextShaper;
/**
* Holds the specific font metrics.
**/
@@ -84,17 +86,32 @@ public:
**/
virtual int getLineHeight() const = 0;
/**
* Gets the spacing of the given unicode glyph.
**/
virtual int getGlyphSpacing(uint32 glyph) const = 0;
/**
* Gets a rasterizer-specific index associated with the given glyph.
**/
virtual int getGlyphIndex(uint32 glyph) const = 0;
/**
* Gets a specific glyph.
* @param glyph The (UNICODE) glyph codepoint to get data for.
**/
virtual GlyphData *getGlyphData(uint32 glyph) const = 0;
GlyphData *getGlyphData(uint32 glyph) const;
/**
* Gets a specific glyph.
* @param text The (UNICODE) glyph character to get the data for.
**/
virtual GlyphData *getGlyphData(const std::string &text) const;
GlyphData *getGlyphData(const std::string &text) const;
/**
* Gets a specific glyph for the given rasterizer glyph index.
**/
virtual GlyphData *getGlyphDataForIndex(int index) const = 0;
/**
* Gets the number of glyphs the rasterizer has data for.
@@ -120,6 +137,10 @@ public:
virtual DataType getDataType() const = 0;
virtual ptrdiff_t getHandle() const { return 0; }
virtual TextShaper *newTextShaper() = 0;
float getDPIScale() const;
protected:
+381
View File
@@ -0,0 +1,381 @@
/**
* Copyright (c) 2006-2023 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 "TextShaper.h"
#include "Rasterizer.h"
#include "common/Exception.h"
#include "libraries/utf8/utf8.h"
namespace love
{
namespace font
{
void getCodepointsFromString(const std::string &text, std::vector<uint32> &codepoints)
{
codepoints.reserve(text.size());
try
{
utf8::iterator<std::string::const_iterator> i(text.begin(), text.begin(), text.end());
utf8::iterator<std::string::const_iterator> end(text.end(), text.begin(), text.end());
while (i != end)
{
uint32 g = *i++;
codepoints.push_back(g);
}
}
catch (utf8::exception &e)
{
throw love::Exception("UTF-8 decoding error: %s", e.what());
}
}
void getCodepointsFromString(const std::vector<ColoredString> &strs, ColoredCodepoints &codepoints)
{
if (strs.empty())
return;
codepoints.cps.reserve(strs[0].str.size());
for (const ColoredString &cstr : strs)
{
// No need to add the color if the string is empty anyway, and the code
// further on assumes no two colors share the same starting position.
if (cstr.str.size() == 0)
continue;
IndexedColor c = { cstr.color, (int)codepoints.cps.size() };
codepoints.colors.push_back(c);
getCodepointsFromString(cstr.str, codepoints.cps);
}
if (codepoints.colors.size() == 1)
{
IndexedColor c = codepoints.colors[0];
if (c.index == 0 && c.color == Colorf(1.0f, 1.0f, 1.0f, 1.0f))
codepoints.colors.pop_back();
}
}
love::Type TextShaper::type("TextShaper", &Object::type);
TextShaper::TextShaper(Rasterizer *rasterizer)
: rasterizers{rasterizer}
, dpiScales{rasterizer->getDPIScale()}
, height(floorf(rasterizer->getHeight() / rasterizer->getDPIScale() + 0.5f))
, lineHeight(1)
, useSpacesForTab(false)
{
if (!rasterizer->hasGlyph('\t'))
useSpacesForTab = true;
}
TextShaper::~TextShaper()
{
}
float TextShaper::getHeight() const
{
return height;
}
void TextShaper::setLineHeight(float h)
{
lineHeight = h;
}
float TextShaper::getLineHeight() const
{
return lineHeight;
}
int TextShaper::getAscent() const
{
return floorf(rasterizers[0]->getAscent() / rasterizers[0]->getDPIScale() + 0.5f);
}
int TextShaper::getDescent() const
{
return floorf(rasterizers[0]->getDescent() / rasterizers[0]->getDPIScale() + 0.5f);
}
float TextShaper::getBaseline() const
{
float ascent = getAscent();
if (ascent != 0.0f)
return ascent;
else if (rasterizers[0]->getDataType() == font::Rasterizer::DATA_TRUETYPE)
return floorf(getHeight() / 1.25f + 0.5f); // 1.25 is magic line height for true type fonts
else
return 0.0f;
}
bool TextShaper::hasGlyph(uint32 glyph) const
{
for (const StrongRef<Rasterizer> &r : rasterizers)
{
if (r->hasGlyph(glyph))
return true;
}
return false;
}
bool TextShaper::hasGlyphs(const std::string &text) const
{
if (text.size() == 0)
return false;
try
{
utf8::iterator<std::string::const_iterator> i(text.begin(), text.begin(), text.end());
utf8::iterator<std::string::const_iterator> end(text.end(), text.begin(), text.end());
while (i != end)
{
uint32 codepoint = *i++;
if (!hasGlyph(codepoint))
return false;
}
}
catch (utf8::exception &e)
{
throw love::Exception("UTF-8 decoding error: %s", e.what());
}
return true;
}
float TextShaper::getKerning(uint32 leftglyph, uint32 rightglyph)
{
uint64 packedglyphs = ((uint64)leftglyph << 32) | (uint64)rightglyph;
const auto it = kerning.find(packedglyphs);
if (it != kerning.end())
return it->second;
float k = 0.0f;
bool found = false;
for (const auto &r : rasterizers)
{
if (r->hasGlyph(leftglyph) && r->hasGlyph(rightglyph))
{
found = true;
k = floorf(r->getKerning(leftglyph, rightglyph) / r->getDPIScale() + 0.5f);
break;
}
}
if (!found)
k = floorf(rasterizers[0]->getKerning(leftglyph, rightglyph) / rasterizers[0]->getDPIScale() + 0.5f);
kerning[packedglyphs] = k;
return k;
}
float TextShaper::getKerning(const std::string &leftchar, const std::string &rightchar)
{
uint32 left = 0;
uint32 right = 0;
try
{
left = utf8::peek_next(leftchar.begin(), leftchar.end());
right = utf8::peek_next(rightchar.begin(), rightchar.end());
}
catch (utf8::exception &e)
{
throw love::Exception("UTF-8 decoding error: %s", e.what());
}
return getKerning(left, right);
}
int TextShaper::getGlyphAdvance(uint32 glyph, GlyphIndex *glyphindex)
{
const auto it = glyphAdvances.find(glyph);
if (it != glyphAdvances.end())
{
if (glyphindex)
*glyphindex = it->second.second;
return it->second.first;
}
int rasterizeri = 0;
uint32 realglyph = glyph;
if (glyph == '\t' && isUsingSpacesForTab())
realglyph = ' ';
for (size_t i = 0; i < rasterizers.size(); i++)
{
if (rasterizers[i]->hasGlyph(realglyph))
{
rasterizeri = (int) i;
break;
}
}
const auto &r = rasterizers[rasterizeri];
int advance = floorf(r->getGlyphSpacing(realglyph) / r->getDPIScale() + 0.5f);
if (glyph == '\t' && realglyph == ' ')
advance *= SPACES_PER_TAB;
GlyphIndex glyphi = {r->getGlyphIndex(realglyph), rasterizeri};
glyphAdvances[glyph] = std::make_pair(advance, glyphi);
if (glyphindex)
*glyphindex = glyphi;
return advance;
}
int TextShaper::getWidth(const std::string &str)
{
if (str.size() == 0) return 0;
ColoredCodepoints codepoints;
getCodepointsFromString(str, codepoints.cps);
TextInfo info;
computeGlyphPositions(codepoints, Range(), Vector2(0.0f, 0.0f), 0.0f, nullptr, nullptr, &info);
return info.width;
}
static size_t findNewline(const ColoredCodepoints &codepoints, size_t start)
{
for (size_t i = start; i < codepoints.cps.size(); i++)
{
if (codepoints.cps[i] == '\n')
{
return i;
}
}
return codepoints.cps.size();
}
void TextShaper::getWrap(const ColoredCodepoints &codepoints, float wraplimit, std::vector<Range> &lineranges, std::vector<int> *linewidths)
{
size_t nextnewline = findNewline(codepoints, 0);
for (size_t i = 0; i < codepoints.cps.size();)
{
if (nextnewline < i)
nextnewline = findNewline(codepoints, i);
if (nextnewline == i) // Empty line.
{
lineranges.push_back(Range());
if (linewidths)
linewidths->push_back(0);
i++;
}
else
{
Range r(i, nextnewline - i);
float width = 0.0f;
int wrapindex = computeWordWrapIndex(codepoints, r, wraplimit, &width);
if (wrapindex >= (int) i)
{
r = Range(i, (size_t) wrapindex + 1 - i);
i = (size_t)wrapindex + 1;
}
else
{
r = Range();
i++;
}
// We've already handled this line, skip the newline character.
if (nextnewline == i)
i++;
lineranges.push_back(r);
if (linewidths)
linewidths->push_back(width);
}
}
}
void TextShaper::getWrap(const std::vector<ColoredString> &text, float wraplimit, std::vector<std::string> &lines, std::vector<int> *linewidths)
{
ColoredCodepoints cps;
getCodepointsFromString(text, cps);
std::vector<Range> codepointranges;
getWrap(cps, wraplimit, codepointranges, linewidths);
std::string line;
for (const auto &range : codepointranges)
{
line.clear();
if (range.isValid())
{
line.reserve(range.getSize());
for (size_t i = range.getMin(); i <= range.getMax(); i++)
{
char character[5] = { '\0' };
char *end = utf8::unchecked::append(cps.cps[i], character);
line.append(character, end - character);
}
}
lines.push_back(line);
}
}
void TextShaper::setFallbacks(const std::vector<Rasterizer*> &fallbacks)
{
for (Rasterizer *r : fallbacks)
{
if (r->getDataType() != rasterizers[0]->getDataType())
throw love::Exception("Font fallbacks must be of the same font type.");
}
// Clear caches.
kerning.clear();
glyphAdvances.clear();
rasterizers.resize(1);
dpiScales.resize(1);
for (Rasterizer *r : fallbacks)
{
rasterizers.push_back(r);
dpiScales.push_back(r->getDPIScale());
}
}
} // font
} // love
+156
View File
@@ -0,0 +1,156 @@
/**
* Copyright (c) 2006-2023 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/Object.h"
#include "common/Vector.h"
#include "common/int.h"
#include "common/Color.h"
#include "common/Range.h"
#include <vector>
#include <string>
#include <unordered_map>
namespace love
{
namespace font
{
class Rasterizer;
struct ColoredString
{
std::string str;
Colorf color;
};
struct IndexedColor
{
Colorf color;
int index;
};
struct ColoredCodepoints
{
std::vector<uint32> cps;
std::vector<IndexedColor> colors;
};
void getCodepointsFromString(const std::string &str, std::vector<uint32> &codepoints);
void getCodepointsFromString(const std::vector<ColoredString> &strs, ColoredCodepoints &codepoints);
class TextShaper : public Object
{
public:
struct GlyphIndex
{
int index;
int rasterizerIndex;
};
struct GlyphPosition
{
Vector2 position;
GlyphIndex glyphIndex;
};
struct TextInfo
{
int width;
int height;
};
// This will be used if the Rasterizer doesn't have a tab character itself.
static const int SPACES_PER_TAB = 4;
static love::Type type;
virtual ~TextShaper();
const std::vector<StrongRef<Rasterizer>> &getRasterizers() const { return rasterizers; }
bool isUsingSpacesForTab() const { return useSpacesForTab; }
float getHeight() const;
/**
* Sets the line height (which should be a number to multiply the font size by,
* example: line height = 1.2 and size = 12 means that rendered line height = 12*1.2)
* @param height The new line height.
**/
void setLineHeight(float height);
/**
* Returns the line height.
**/
float getLineHeight() const;
// Extra font metrics
int getAscent() const;
int getDescent() const;
float getBaseline() const;
bool hasGlyph(uint32 glyph) const;
bool hasGlyphs(const std::string &text) const;
float getKerning(uint32 leftglyph, uint32 rightglyph);
float getKerning(const std::string &leftchar, const std::string &rightchar);
int getGlyphAdvance(uint32 glyph, GlyphIndex *glyphindex = nullptr);
int getWidth(const std::string &str);
void getWrap(const std::vector<ColoredString> &text, float wraplimit, std::vector<std::string> &lines, std::vector<int> *linewidths = nullptr);
void getWrap(const ColoredCodepoints &codepoints, float wraplimit, std::vector<Range> &lineranges, std::vector<int> *linewidths = nullptr);
virtual void setFallbacks(const std::vector<Rasterizer *> &fallbacks);
virtual void computeGlyphPositions(const ColoredCodepoints &codepoints, Range range, Vector2 offset, float extraspacing, std::vector<GlyphPosition> *positions, std::vector<IndexedColor> *colors, TextInfo *info) = 0;
virtual int computeWordWrapIndex(const ColoredCodepoints &codepoints, Range range, float wraplimit, float *width) = 0;
protected:
TextShaper(Rasterizer *rasterizer);
static inline bool isWhitespace(uint32 codepoint) { return codepoint == ' ' || codepoint == '\t'; }
std::vector<StrongRef<Rasterizer>> rasterizers;
std::vector<float> dpiScales;
private:
int height;
float lineHeight;
bool useSpacesForTab;
// maps glyphs to advance and glyph+rasterizer index.
std::unordered_map<uint32, std::pair<int, GlyphIndex>> glyphAdvances;
// map of left/right glyph pairs to horizontal kerning.
std::unordered_map<uint64, float> kerning;
}; // TextShaper
} // font
} // love
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,415 @@
/**
* Copyright (c) 2006-2023 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 "HarfbuzzShaper.h"
#include "TrueTypeRasterizer.h"
#include "common/Optional.h"
// harfbuzz
#include <hb.h>
#include <hb-ft.h>
namespace love
{
namespace font
{
namespace freetype
{
HarfbuzzShaper::HarfbuzzShaper(TrueTypeRasterizer *rasterizer)
: TextShaper(rasterizer)
, spaceGlyphIndex()
, tabSpacesAdvanceX(0)
, tabSpacesAdvanceY(0)
{
hbFonts.push_back(hb_ft_font_create_referenced((FT_Face)rasterizer->getHandle()));
hbBuffers.push_back(hb_buffer_create());
if (hbFonts[0] == nullptr || hbFonts[0] == hb_font_get_empty())
throw love::Exception("Could not create Harfbuzz font object.");
if (hbBuffers[0] == nullptr || hbBuffers[0] == hb_buffer_get_empty())
throw love::Exception("Could not create Harfbuzz buffer object.");
updateSpacesForTabInfo();
}
HarfbuzzShaper::~HarfbuzzShaper()
{
for (hb_buffer_t *buffer : hbBuffers)
hb_buffer_destroy(buffer);
for (hb_font_t *font : hbFonts)
hb_font_destroy(font);
}
void HarfbuzzShaper::setFallbacks(const std::vector<Rasterizer*> &fallbacks)
{
for (size_t i = 1; i < rasterizers.size(); i++)
{
hb_buffer_destroy(hbBuffers[i]);
hb_font_destroy(hbFonts[i]);
}
TextShaper::setFallbacks(fallbacks);
hbFonts.resize(rasterizers.size());
hbBuffers.resize(rasterizers.size());
for (size_t i = 1; i < rasterizers.size(); i++)
{
hbFonts[i] = hb_ft_font_create_referenced((FT_Face)rasterizers[i]->getHandle());
hbBuffers[i] = hb_buffer_create();
}
updateSpacesForTabInfo();
}
void HarfbuzzShaper::updateSpacesForTabInfo()
{
if (!isUsingSpacesForTab())
return;
hb_codepoint_t glyphid = 0;
for (size_t i = 0; i < hbFonts.size(); i++)
{
hb_font_t *hbfont = hbFonts[i];
if (hb_font_get_glyph(hbfont, ' ', 0, &glyphid))
{
spaceGlyphIndex.index = glyphid;
spaceGlyphIndex.rasterizerIndex = i;
tabSpacesAdvanceX = hb_font_get_glyph_h_advance(hbfont, glyphid) * SPACES_PER_TAB;
tabSpacesAdvanceY = hb_font_get_glyph_v_advance(hbfont, glyphid) * SPACES_PER_TAB;
break;
}
}
}
bool HarfbuzzShaper::isValidGlyph(uint32 glyphindex, const std::vector<uint32> &codepoints, uint32 codepointindex)
{
if (glyphindex != 0)
return true;
uint32 codepoint = codepoints[codepointindex];
if (codepoint == '\n' || codepoint == '\r' || (codepoint == '\t' && isUsingSpacesForTab()))
return true;
return false;
}
void HarfbuzzShaper::computeBufferRanges(const ColoredCodepoints &codepoints, Range range, std::vector<BufferRange> &bufferranges)
{
bufferranges.clear();
if (codepoints.cps.size() == 0)
return;
// Less computation for the typical case (no fallback fonts).
if (rasterizers.size() == 1)
{
hb_buffer_reset(hbBuffers[0]);
hb_buffer_add_codepoints(hbBuffers[0], codepoints.cps.data(), codepoints.cps.size(), (unsigned int)range.getOffset(), (int)range.getSize());
// TODO: Expose APIs for direction and script?
hb_buffer_guess_segment_properties(hbBuffers[0]);
hb_shape(hbFonts[0], hbBuffers[0], nullptr, 0);
bufferranges.push_back({0, (int) range.first, Range(0, hb_buffer_get_length(hbBuffers[0]))});
return;
}
std::vector<Range> fallbackranges = { range };
// For each font, figure out the ranges of valid glyphs in the given string,
// and add the rest to a list to be shaped by the next fallback font.
// Harfbuzz doesn't have its own fallback API.
for (size_t rasti = 0; rasti < rasterizers.size(); rasti++)
{
hb_buffer_t *hbb = hbBuffers[rasti];
hb_buffer_reset(hbb);
for (Range r : fallbackranges)
hb_buffer_add_codepoints(hbb, codepoints.cps.data(), codepoints.cps.size(), (unsigned int)r.getOffset(), (int)r.getSize());
hb_buffer_guess_segment_properties(hbb);
hb_shape(hbFonts[rasti], hbb, nullptr, 0);
int glyphcount = (int)hb_buffer_get_length(hbb);
const hb_glyph_info_t *glyphinfos = hb_buffer_get_glyph_infos(hbb, nullptr);
hb_direction_t direction = hb_buffer_get_direction(hbb);
fallbackranges.clear();
for (int i = 0; i < glyphcount; i++)
{
if (isValidGlyph(glyphinfos[i].codepoint, codepoints.cps, glyphinfos[i].cluster))
{
if (bufferranges.empty() || bufferranges.back().index != rasti || bufferranges.back().range.getMax() + 1 != i)
bufferranges.push_back({(int)rasti, (int)glyphinfos[i].cluster, Range(i, 1)});
else
bufferranges.back().range.last++;
}
else if (rasti == rasterizers.size() - 1)
{
// Use the first font for remaining invalid glyphs when no
// fallback font supports them.
if (bufferranges.empty() || bufferranges.back().index != 0 || bufferranges.back().range.getMax() + 1 != i)
bufferranges.push_back({0, (int)glyphinfos[i].cluster, Range(i, 1)});
else
bufferranges.back().range.last++;
}
else
{
// Harfbuzz puts RTL text into the buffer in reverse order, so
// it'll start with the last cluster (character index).
if (fallbackranges.empty() || (direction == HB_DIRECTION_RTL ? fallbackranges.back().getMin() : fallbackranges.back().getMax()) != glyphinfos[i - 1].cluster)
fallbackranges.push_back(Range(glyphinfos[i].cluster, 1));
else
fallbackranges.back().encapsulate(glyphinfos[i].cluster);
}
}
}
std::sort(bufferranges.begin(), bufferranges.end(), [](const BufferRange &a, const BufferRange &b)
{
if (a.codepointStart != b.codepointStart)
return a.codepointStart < b.codepointStart;
if (a.index != b.index)
return a.index < b.index;
return a.range.first < b.range.first;
});
}
void HarfbuzzShaper::computeGlyphPositions(const ColoredCodepoints &codepoints, Range range, Vector2 offset, float extraspacing, std::vector<GlyphPosition> *positions, std::vector<IndexedColor> *colors, TextInfo *info)
{
if (!range.isValid() && !codepoints.cps.empty())
range = Range(0, codepoints.cps.size());
offset.y += getBaseline();
Vector2 curpos = offset;
int colorindex = 0;
int ncolors = (int)codepoints.colors.size();
Optional<Colorf> colorToAdd;
// Make sure the right color is applied to the start of the glyph list,
// when the start isn't 0.
if (colors && range.getOffset() > 0 && !codepoints.colors.empty())
{
for (; colorindex < ncolors; colorindex++)
{
if (codepoints.colors[colorindex].index >= (int) range.getOffset())
break;
colorToAdd.set(codepoints.colors[colorindex].color);
}
}
std::vector<BufferRange> bufferranges;
computeBufferRanges(codepoints, range, bufferranges);
int maxwidth = (int)curpos.x;
for (const auto &bufferrange : bufferranges)
{
if (positions)
positions->reserve(positions->size() + bufferrange.range.getSize());
hb_buffer_t *hbbuffer = hbBuffers[bufferrange.index];
const hb_glyph_info_t *glyphinfos = hb_buffer_get_glyph_infos(hbbuffer, nullptr);
hb_glyph_position_t *glyphpositions = hb_buffer_get_glyph_positions(hbbuffer, nullptr);
hb_direction_t direction = hb_buffer_get_direction(hbbuffer);
for (size_t i = bufferrange.range.first; i <= bufferrange.range.last; i++)
{
const hb_glyph_info_t &info = glyphinfos[i];
hb_glyph_position_t &glyphpos = glyphpositions[i];
// TODO: this doesn't handle situations where the user inserted a color
// change in the middle of some characters that get combined into a single
// cluster.
if (colors && colorindex < ncolors && codepoints.colors[colorindex].index == info.cluster)
{
colorToAdd.set(codepoints.colors[colorindex].color);
colorindex++;
}
uint32 clustercodepoint = codepoints.cps[info.cluster];
// Harfbuzz doesn't handle newlines itself, but it does leave them in
// the glyph list so we can do it manually.
if (clustercodepoint == '\n')
{
if (curpos.x > maxwidth)
maxwidth = (int)curpos.x;
// Wrap newline, but do not output a position for it.
curpos.y += floorf(getHeight() * getLineHeight() + 0.5f);
curpos.x = offset.x;
continue;
}
// Ignore carriage returns
if (clustercodepoint == '\r')
continue;
// This is a glyph index at this point, despite the name.
GlyphIndex gindex = { (int) info.codepoint, bufferrange.index };
if (clustercodepoint == '\t' && isUsingSpacesForTab())
{
gindex = spaceGlyphIndex;
// This should be safe to overwrite.
// TODO: RTL support?
glyphpos.x_offset = 0;
glyphpos.y_offset = 0;
glyphpos.x_advance = HB_DIRECTION_IS_HORIZONTAL(direction) ? tabSpacesAdvanceX : 0;
glyphpos.y_advance = HB_DIRECTION_IS_VERTICAL(direction) ? tabSpacesAdvanceY : 0;
}
if (colorToAdd.hasValue && colors && positions)
{
IndexedColor c = {colorToAdd.value, (int) positions->size()};
colors->push_back(c);
colorToAdd.clear();
}
if (positions)
{
GlyphPosition p = { curpos, gindex };
// Harfbuzz position coordinate systems are based on the given font.
// Freetype uses 26.6 fixed point coordinates, so harfbuzz does too.
p.position.x += floorf((glyphpos.x_offset >> 6) / dpiScales[0] + 0.5f);
p.position.y += floorf((glyphpos.y_offset >> 6) / dpiScales[0] + 0.5f);
positions->push_back(p);
}
curpos.x += floorf((glyphpos.x_advance >> 6) / dpiScales[0] + 0.5f);
curpos.y += floorf((glyphpos.y_advance >> 6) / dpiScales[0] + 0.5f);
// Account for extra spacing given to space characters.
if (clustercodepoint == ' ' && extraspacing != 0.0f)
curpos.x = floorf(curpos.x + extraspacing);
}
}
if (curpos.x > maxwidth)
maxwidth = (int)curpos.x;
if (info != nullptr)
{
info->width = maxwidth - offset.x;
info->height = curpos.y - offset.y;
if (curpos.x > offset.x)
info->height += floorf(getHeight() * getLineHeight() + 0.5f);
}
}
int HarfbuzzShaper::computeWordWrapIndex(const ColoredCodepoints &codepoints, Range range, float wraplimit, float *width)
{
if (!range.isValid())
range = Range(0, codepoints.cps.size());
float w = 0.0f;
float outwidth = 0.0f;
float widthbeforelastspace = 0.0f;
int wrapindex = -1;
int lastspaceindex = -1;
uint32 prevcodepoint = 0;
std::vector<BufferRange> bufferranges;
computeBufferRanges(codepoints, range, bufferranges);
for (const auto &bufferrange : bufferranges)
{
hb_buffer_t *hbbuffer = hbBuffers[bufferrange.index];
const hb_glyph_info_t *glyphinfos = hb_buffer_get_glyph_infos(hbbuffer, nullptr);
hb_glyph_position_t *glyphpositions = hb_buffer_get_glyph_positions(hbbuffer, nullptr);
hb_direction_t direction = hb_buffer_get_direction(hbbuffer);
for (size_t i = bufferrange.range.first; i <= bufferrange.range.last; i++)
{
const hb_glyph_info_t &info = glyphinfos[i];
hb_glyph_position_t &glyphpos = glyphpositions[i];
uint32 clustercodepoint = codepoints.cps[info.cluster];
if (clustercodepoint == '\r')
{
prevcodepoint = clustercodepoint;
continue;
}
if (clustercodepoint == '\t' && isUsingSpacesForTab())
{
// This should be safe to overwrite.
// TODO: RTL support?
glyphpos.x_offset = 0;
glyphpos.y_offset = 0;
glyphpos.x_advance = HB_DIRECTION_IS_HORIZONTAL(direction) ? tabSpacesAdvanceX : 0;
glyphpos.y_advance = HB_DIRECTION_IS_VERTICAL(direction) ? tabSpacesAdvanceY : 0;
}
float newwidth = w + floorf((glyphpos.x_advance >> 6) / dpiScales[0] + 0.5f);
// Only wrap when there's a non-space character.
if (newwidth > wraplimit && !isWhitespace(clustercodepoint))
{
// Rewind to the last seen space when wrapping.
if (lastspaceindex != -1)
{
wrapindex = lastspaceindex;
outwidth = widthbeforelastspace;
}
break;
}
// Don't count trailing spaces in the output width.
if (isWhitespace(clustercodepoint))
{
lastspaceindex = info.cluster;
if (!isWhitespace(prevcodepoint))
widthbeforelastspace = w;
}
else
outwidth = newwidth;
w = newwidth;
prevcodepoint = clustercodepoint;
wrapindex = info.cluster;
}
}
if (width)
*width = outwidth;
return wrapindex;
}
} // freetype
} // font
} // love
@@ -0,0 +1,76 @@
/**
* Copyright (c) 2006-2023 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 "font/TextShaper.h"
extern "C"
{
typedef struct hb_font_t hb_font_t;
typedef struct hb_buffer_t hb_buffer_t;
}
namespace love
{
namespace font
{
namespace freetype
{
class TrueTypeRasterizer;
class HarfbuzzShaper : public love::font::TextShaper
{
public:
HarfbuzzShaper(TrueTypeRasterizer *rasterizer);
virtual ~HarfbuzzShaper();
void setFallbacks(const std::vector<Rasterizer *> &fallbacks) override;
void computeGlyphPositions(const ColoredCodepoints &codepoints, Range range, Vector2 offset, float extraspacing, std::vector<GlyphPosition> *positions, std::vector<IndexedColor> *colors, TextInfo *info) override;
int computeWordWrapIndex(const ColoredCodepoints &codepoints, Range range, float wraplimit, float *width) override;
private:
struct BufferRange
{
int index;
int codepointStart;
Range range;
};
void updateSpacesForTabInfo();
bool isValidGlyph(uint32 glyphindex, const std::vector<uint32> &codepoints, uint32 codepointindex);
void computeBufferRanges(const ColoredCodepoints &codepoints, Range range, std::vector<BufferRange> &bufferranges);
std::vector<hb_font_t *> hbFonts;
std::vector<hb_buffer_t *> hbBuffers;
GlyphIndex spaceGlyphIndex;
int tabSpacesAdvanceX;
int tabSpacesAdvanceY;
}; // HarfbuzzShaper
} // freetype
} // font
} // love
@@ -20,6 +20,7 @@
// LOVE
#include "TrueTypeRasterizer.h"
#include "HarfbuzzShaper.h"
#include "common/Exception.h"
// C
@@ -75,7 +76,30 @@ int TrueTypeRasterizer::getLineHeight() const
return (int)(getHeight() * 1.25);
}
GlyphData *TrueTypeRasterizer::getGlyphData(uint32 glyph) const
int TrueTypeRasterizer::getGlyphSpacing(uint32 glyph) const
{
FT_Glyph ftglyph;
FT_Error err = FT_Err_Ok;
FT_UInt loadoption = hintingToLoadOption(hinting);
// Initialize
err = FT_Load_Glyph(face, FT_Get_Char_Index(face, glyph), FT_LOAD_DEFAULT | loadoption);
if (err != FT_Err_Ok)
return 0;
err = FT_Get_Glyph(face->glyph, &ftglyph);
if (err != FT_Err_Ok)
return 0;
return (int)(ftglyph->advance.x >> 16);
}
int TrueTypeRasterizer::getGlyphIndex(uint32 glyph) const
{
return FT_Get_Char_Index(face, glyph);
}
GlyphData *TrueTypeRasterizer::getGlyphDataForIndex(int index) const
{
love::font::GlyphMetrics glyphMetrics = {};
FT_Glyph ftglyph;
@@ -84,7 +108,7 @@ GlyphData *TrueTypeRasterizer::getGlyphData(uint32 glyph) const
FT_UInt loadoption = hintingToLoadOption(hinting);
// Initialize
err = FT_Load_Glyph(face, FT_Get_Char_Index(face, glyph), FT_LOAD_DEFAULT | loadoption);
err = FT_Load_Glyph(face, index, FT_LOAD_DEFAULT | loadoption);
if (err != FT_Err_Ok)
throw love::Exception("TrueType Font glyph error: FT_Load_Glyph failed (0x%x)", err);
@@ -115,7 +139,8 @@ GlyphData *TrueTypeRasterizer::getGlyphData(uint32 glyph) const
}
}
FT_BitmapGlyph bitmap_glyph = (FT_BitmapGlyph) ftglyph;
FT_Bitmap &bitmap = bitmap_glyph->bitmap;//just to make things easier
const FT_Bitmap &bitmap = bitmap_glyph->bitmap; //just to make things easier
// Get metrics
glyphMetrics.bearingX = bitmap_glyph->left;
glyphMetrics.bearingY = bitmap_glyph->top;
@@ -123,7 +148,8 @@ GlyphData *TrueTypeRasterizer::getGlyphData(uint32 glyph) const
glyphMetrics.width = bitmap.width;
glyphMetrics.advance = (int) (ftglyph->advance.x >> 16);
GlyphData *glyphData = new GlyphData(glyph, glyphMetrics, PIXELFORMAT_LA8);
// TODO: https://stackoverflow.com/questions/60526004/how-to-get-glyph-unicode-using-freetype/69730502#69730502
GlyphData *glyphData = new GlyphData(0, glyphMetrics, PIXELFORMAT_LA8_UNORM);
const uint8 *pixels = bitmap.buffer;
uint8 *dest = (uint8 *) glyphData->getData();
@@ -196,6 +222,11 @@ Rasterizer::DataType TrueTypeRasterizer::getDataType() const
return DATA_TRUETYPE;
}
TextShaper *TrueTypeRasterizer::newTextShaper()
{
return new HarfbuzzShaper(this);
}
bool TrueTypeRasterizer::accepts(FT_Library library, love::Data *data)
{
const FT_Byte *fbase = (const FT_Byte *) data->getData();
@@ -49,11 +49,16 @@ public:
// Implement Rasterizer
int getLineHeight() const override;
GlyphData *getGlyphData(uint32 glyph) const override;
int getGlyphSpacing(uint32 glyph) const override;
int getGlyphIndex(uint32 glyph) const override;
GlyphData *getGlyphDataForIndex(int index) const override;
int getGlyphCount() const override;
bool hasGlyph(uint32 glyph) const override;
float getKerning(uint32 leftglyph, uint32 rightglyph) const override;
DataType getDataType() const override;
TextShaper *newTextShaper() override;
ptrdiff_t getHandle() const override { return (ptrdiff_t) face; }
static bool accepts(FT_Library library, love::Data *data);
+1 -1
View File
@@ -72,7 +72,7 @@ int w_newTrueTypeRasterizer(lua_State *L)
if (lua_type(L, 1) == LUA_TNUMBER || lua_isnone(L, 1))
{
// First argument is a number: use the default TrueType font.
int size = (int) luaL_optinteger(L, 1, 12);
int size = (int) luaL_optinteger(L, 1, 13);
const char *hintstr = lua_isnoneornil(L, 2) ? nullptr : luaL_checkstring(L, 2);
if (hintstr && !TrueTypeRasterizer::getConstant(hintstr, hinting))
+285 -6
View File
@@ -19,24 +19,303 @@
**/
#include "Buffer.h"
#include "Graphics.h"
#include "common/memory.h"
namespace love
{
namespace graphics
{
Buffer::Buffer(size_t size, BufferType type, vertex::Usage usage, uint32 mapflags)
: size(size)
, type(type)
, usage(usage)
, map_flags(mapflags)
, is_mapped(false)
love::Type Buffer::type("GraphicsBuffer", &Object::type);
Buffer::Buffer(Graphics *gfx, const Settings &settings, const std::vector<DataDeclaration> &bufferformat, size_t size, size_t arraylength)
: arrayLength(0)
, arrayStride(0)
, size(size)
, usageFlags(settings.usageFlags)
, dataUsage(settings.dataUsage)
, mapped(false)
, mappedType(MAP_WRITE_INVALIDATE)
, immutable(false)
{
if (size == 0 && arraylength == 0)
throw love::Exception("Size or array length must be specified.");
if (bufferformat.size() == 0)
throw love::Exception("Data format must contain values.");
const auto &caps = gfx->getCapabilities();
bool supportsGLSL3 = caps.features[Graphics::FEATURE_GLSL3];
bool indexbuffer = usageFlags & BUFFERUSAGEFLAG_INDEX;
bool vertexbuffer = usageFlags & BUFFERUSAGEFLAG_VERTEX;
bool texelbuffer = usageFlags & BUFFERUSAGEFLAG_TEXEL;
bool storagebuffer = usageFlags & BUFFERUSAGEFLAG_SHADER_STORAGE;
bool indirectbuffer = usageFlags & BUFFERUSAGEFLAG_INDIRECT_ARGUMENTS;
if (texelbuffer && !caps.features[Graphics::FEATURE_TEXEL_BUFFER])
throw love::Exception("Texel buffers are not supported on this system.");
if (storagebuffer && !caps.features[Graphics::FEATURE_GLSL4])
throw love::Exception("Shader Storage buffers are not supported on this system (GLSL 4 support is necessary.)");
if (storagebuffer && dataUsage == BUFFERDATAUSAGE_STREAM)
throw love::Exception("Buffers created with 'stream' data usage cannot be used as a shader storage buffer.");
if (indirectbuffer && !caps.features[Graphics::FEATURE_INDIRECT_DRAW])
throw love::Exception("Indirect argument buffers are not supported on this system.");
if (dataUsage == BUFFERDATAUSAGE_READBACK && (indexbuffer || vertexbuffer || texelbuffer || storagebuffer || indirectbuffer))
throw love::Exception("Buffers created with 'readback' data usage cannot be index, vertex, texel, shaderstorage, or indirectarguments buffer types.");
size_t offset = 0;
size_t stride = 0;
size_t structurealignment = 1;
for (const DataDeclaration &decl : bufferformat)
{
DataMember member(decl);
DataFormat format = member.decl.format;
const DataFormatInfo &info = member.info;
if (indexbuffer)
{
if (!caps.features[Graphics::FEATURE_INDEX_BUFFER_32BIT] && format == DATAFORMAT_UINT32)
throw love::Exception("32 bit index buffer formats are not supported on this system.");
if (format != DATAFORMAT_UINT16 && format != DATAFORMAT_UINT32)
throw love::Exception("Index buffers only support uint16 and uint32 data types.");
if (bufferformat.size() > 1)
throw love::Exception("Index buffers only support a single value per element.");
if (decl.arrayLength > 0)
throw love::Exception("Arrays are not supported in index buffers.");
}
if (vertexbuffer)
{
if (decl.arrayLength > 0)
throw love::Exception("Arrays are not supported in vertex buffers.");
if (info.isMatrix)
throw love::Exception("Matrix types are not supported in vertex buffers.");
if (info.baseType == DATA_BASETYPE_BOOL)
throw love::Exception("Bool types are not supported in vertex buffers.");
if ((info.baseType == DATA_BASETYPE_INT || info.baseType == DATA_BASETYPE_UINT) && !supportsGLSL3)
throw love::Exception("Integer vertex attribute data types require GLSL 3 support.");
if (decl.name.empty())
throw love::Exception("Vertex buffer attributes must have a name.");
}
if (texelbuffer)
{
if (format != bufferformat[0].format)
throw love::Exception("All values in a texel buffer must have the same format.");
if (decl.arrayLength > 0)
throw love::Exception("Arrays are not supported in texel buffers.");
if (info.isMatrix)
throw love::Exception("Matrix types are not supported in texel buffers.");
if (info.baseType == DATA_BASETYPE_BOOL)
throw love::Exception("Bool types are not supported in texel buffers.");
if (info.components == 3)
throw love::Exception("3-component formats are not supported in texel buffers.");
if (info.baseType == DATA_BASETYPE_SNORM)
throw love::Exception("Signed normalized formats are not supported in texel buffers.");
}
size_t memberoffset = offset;
size_t membersize = member.info.size;
// Storage buffers are always treated as being an array of a structure.
// The structure's contents are the buffer format declaration.
if (storagebuffer)
{
// TODO: We can support these.
if (decl.arrayLength > 0)
throw love::Exception("Arrays are not currently supported in shader storage buffers.");
if (info.baseType == DATA_BASETYPE_BOOL)
throw love::Exception("Bool types are not supported in shader storage buffers.");
if (info.baseType == DATA_BASETYPE_UNORM || info.baseType == DATA_BASETYPE_SNORM)
throw love::Exception("Normalized formats are not supported in shader storage buffers.");
size_t alignment = 1;
// GLSL's std430 packing rules. We also assume all matrices are
// column-major.
// https://www.khronos.org/registry/OpenGL/specs/gl/glspec46.core.pdf
// "If the member is a column-major matrix with C columns and R rows,
// the matrix is stored identically to an array of C column vectors
// with R components each".
// "If the member is a three-component vector with components
// consuming N basic machine units, the base alignment is 4N."
int c = info.isMatrix ? info.matrixRows : info.components;
alignment = c == 3 ? 4 * info.componentSize : c * info.componentSize;
// std430 will effectively turn a floatmat3x3 into a floatmat4x3
// because of its vec3 padding rules. For now we'd rather not
// support those formats at all, because it's not easy for users to
// deal with.
if (alignment != c * info.componentSize && (decl.arrayLength > 0 || info.isMatrix))
{
const char *fstr = "unknown";
getConstant(decl.format, fstr);
throw love::Exception("Data format %s%s is not currently supported in shader storage buffers.", fstr, decl.arrayLength > 0 ? " array" : "");
}
// "If the member is a structure, the base alignment of the structure
// is N, where N is the largest base alignment value of any of its
// members"
structurealignment = std::max(structurealignment, alignment);
memberoffset = alignUp(memberoffset, alignment);
if (memberoffset != offset && (indexbuffer || vertexbuffer || texelbuffer))
throw love::Exception("Cannot create Buffer:\nInternal alignment of member '%s' is preventing Buffer from being created as both a shader storage buffer and other buffer types\nMember byte offset needed for shader storage buffer: %d\nMember byte offset needed for other buffer types: %d",
member.decl.name.c_str(), memberoffset, offset);
}
if (indirectbuffer)
{
if (info.isMatrix || info.components != 1
|| (info.baseType != DATA_BASETYPE_UINT && info.baseType != DATA_BASETYPE_INT))
{
throw love::Exception("Indirect argument buffers must use single-component int or uint types.");
}
if (bufferformat.size() > 5)
throw love::Exception("Indirect argument buffers only support up to 5 values per array element.");
}
member.offset = memberoffset;
member.size = membersize;
offset = member.offset + member.size;
dataMembers.push_back(member);
}
stride = alignUp(offset, structurealignment);
if (storagebuffer && (indexbuffer || vertexbuffer || texelbuffer))
{
if (stride != offset)
throw love::Exception("Cannot create Buffer:\nBuffer used as a shader storage buffer would have a different number of bytes per array element (%d) than when used as other buffer types (%d)",
stride, offset);
}
if (storagebuffer && stride > SHADER_STORAGE_BUFFER_MAX_STRIDE)
throw love::Exception("Shader storage buffers cannot have more than %d bytes within each array element.", SHADER_STORAGE_BUFFER_MAX_STRIDE);
if (size != 0)
{
size_t remainder = size % stride;
if (remainder > 0)
size += stride - remainder;
arraylength = size / stride;
}
else
{
size = arraylength * stride;
}
this->arrayStride = stride;
this->arrayLength = arraylength;
this->size = size;
if (texelbuffer && arraylength * dataMembers.size() > caps.limits[Graphics::LIMIT_TEXEL_BUFFER_SIZE])
throw love::Exception("Cannot create texel buffer: total number of values in the buffer (%d * %d) is too large for this system (maximum %d).",
(int) dataMembers.size(), (int) arraylength, caps.limits[Graphics::LIMIT_TEXEL_BUFFER_SIZE]);
}
Buffer::~Buffer()
{
}
int Buffer::getDataMemberIndex(const std::string &name) const
{
for (size_t i = 0; i < dataMembers.size(); i++)
{
if (dataMembers[i].decl.name == name)
return (int) i;
}
return -1;
}
std::vector<Buffer::DataDeclaration> Buffer::getCommonFormatDeclaration(CommonFormat format)
{
switch (format)
{
case CommonFormat::NONE:
return {};
case CommonFormat::XYf:
return {
{ getConstant(ATTRIB_POS), DATAFORMAT_FLOAT_VEC2 }
};
case CommonFormat::XYZf:
return {
{ getConstant(ATTRIB_POS), DATAFORMAT_FLOAT_VEC3 }
};
case CommonFormat::RGBAub:
return {
{ getConstant(ATTRIB_COLOR), DATAFORMAT_UNORM8_VEC4 }
};
case CommonFormat::STf_RGBAub:
return {
{ getConstant(ATTRIB_TEXCOORD), DATAFORMAT_FLOAT_VEC2 },
{ getConstant(ATTRIB_COLOR), DATAFORMAT_UNORM8_VEC4 },
};
case CommonFormat::STPf_RGBAub:
return {
{ getConstant(ATTRIB_TEXCOORD), DATAFORMAT_FLOAT_VEC3 },
{ getConstant(ATTRIB_COLOR), DATAFORMAT_UNORM8_VEC4 },
};
case CommonFormat::XYf_STf:
return {
{ getConstant(ATTRIB_POS), DATAFORMAT_FLOAT_VEC2 },
{ getConstant(ATTRIB_TEXCOORD), DATAFORMAT_FLOAT_VEC2 },
};
case CommonFormat::XYf_STPf:
return {
{ getConstant(ATTRIB_POS), DATAFORMAT_FLOAT_VEC2 },
{ getConstant(ATTRIB_TEXCOORD), DATAFORMAT_FLOAT_VEC3 },
};
case CommonFormat::XYf_STf_RGBAub:
return {
{ getConstant(ATTRIB_POS), DATAFORMAT_FLOAT_VEC2 },
{ getConstant(ATTRIB_TEXCOORD), DATAFORMAT_FLOAT_VEC2 },
{ getConstant(ATTRIB_COLOR), DATAFORMAT_UNORM8_VEC4 },
};
case CommonFormat::XYf_STus_RGBAub:
return {
{ getConstant(ATTRIB_POS), DATAFORMAT_FLOAT_VEC2 },
{ getConstant(ATTRIB_TEXCOORD), DATAFORMAT_UNORM16_VEC2 },
{ getConstant(ATTRIB_COLOR), DATAFORMAT_UNORM8_VEC4 },
};
case CommonFormat::XYf_STPf_RGBAub:
return {
{ getConstant(ATTRIB_POS), DATAFORMAT_FLOAT_VEC2 },
{ getConstant(ATTRIB_TEXCOORD), DATAFORMAT_FLOAT_VEC2 },
{ getConstant(ATTRIB_COLOR), DATAFORMAT_UNORM8_VEC4 },
};
}
return {};
}
} // graphics
} // love
+103 -60
View File
@@ -23,127 +23,170 @@
// LOVE
#include "common/config.h"
#include "common/int.h"
#include "common/Object.h"
#include "vertex.h"
#include "Resource.h"
// C
#include <stddef.h>
#include <string>
#include <vector>
namespace love
{
namespace graphics
{
class Graphics;
/**
* A block of GPU-owned memory. Currently meant for internal use.
* A block of GPU-owned memory.
**/
class Buffer : public Resource
class Buffer : public love::Object, public Resource
{
public:
enum MapFlags
static love::Type type;
static const size_t SHADER_STORAGE_BUFFER_MAX_STRIDE = 2048;
enum MapType
{
MAP_EXPLICIT_RANGE_MODIFY = (1 << 0), // see setMappedRangeModified.
MAP_READ = (1 << 1),
MAP_WRITE_INVALIDATE,
MAP_READ_ONLY,
};
Buffer(size_t size, BufferType type, vertex::Usage usage, uint32 mapflags);
struct DataDeclaration
{
std::string name;
DataFormat format;
int arrayLength;
DataDeclaration(const std::string &name, DataFormat format, int arrayLength = 0)
: name(name)
, format(format)
, arrayLength(arrayLength)
{}
};
struct DataMember
{
DataDeclaration decl;
DataFormatInfo info;
size_t offset;
size_t size;
DataMember(const DataDeclaration &decl)
: decl(decl)
, info(getDataFormatInfo(decl.format))
, offset(0)
, size(0)
{}
};
struct Settings
{
BufferUsageFlags usageFlags;
BufferDataUsage dataUsage;
bool zeroInitialize;
Settings(uint32 usageflags, BufferDataUsage dataUsage)
: usageFlags((BufferUsageFlags)usageflags)
, dataUsage(dataUsage)
, zeroInitialize(false)
{}
};
Buffer(Graphics *gfx, const Settings &settings, const std::vector<DataDeclaration> &format, size_t size, size_t arraylength);
virtual ~Buffer();
size_t getSize() const { return size; }
BufferUsageFlags getUsageFlags() const { return usageFlags; }
BufferDataUsage getDataUsage() const { return dataUsage; }
bool isMapped() const { return mapped; }
BufferType getType() const { return type; }
size_t getArrayLength() const { return arrayLength; }
size_t getArrayStride() const { return arrayStride; }
const std::vector<DataMember> &getDataMembers() const { return dataMembers; }
const DataMember &getDataMember(int index) const { return dataMembers[index]; }
size_t getMemberOffset(int index) const { return dataMembers[index].offset; }
int getDataMemberIndex(const std::string &name) const;
vertex::Usage getUsage() const { return usage; }
bool isMapped() const { return is_mapped; }
void setImmutable(bool immutable) { this->immutable = immutable; };
bool isImmutable() const { return immutable; }
/**
* Map the Buffer to client memory.
*
* This can be faster for large changes to the buffer. For smaller
* changes, see fill().
* Map a portion of the Buffer to client memory.
*/
virtual void *map() = 0;
virtual void *map(MapType map, size_t offset, size_t size) = 0;
/**
* Unmap a previously mapped Buffer. The buffer must be unmapped when used
* to draw.
*/
virtual void unmap() = 0;
virtual void unmap(size_t usedoffset, size_t usedsize) = 0;
/**
* Marks a range of mapped data as modified.
* NOTE: Buffer::fill calls this internally for you.
**/
virtual void setMappedRangeModified(size_t offset, size_t size) = 0;
/**
* Fill a portion of the buffer with data and marks the range as modified.
*
* @param offset The offset in the GLBuffer to store the data.
* @param size The size of the incoming data.
* @param data Pointer to memory to copy data from.
* 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 the contents of this Buffer to another Buffer object.
**/
virtual void copyTo(size_t offset, size_t size, Buffer *other, size_t otheroffset) = 0;
* Reset the given portion of this buffer's data to 0.
*/
virtual void clear(size_t offset, size_t size) = 0;
uint32 getMapFlags() const { return map_flags; }
/**
* Copy a portion of this Buffer's data to another buffer, using the GPU.
**/
virtual void copyTo(Buffer *dest, size_t sourceoffset, size_t destoffset, size_t size) = 0;
/**
* Texel buffers may use an additional texture handle as well as a buffer
* handle.
**/
virtual ptrdiff_t getTexelBufferHandle() const = 0;
static std::vector<DataDeclaration> getCommonFormatDeclaration(CommonFormat format);
class Mapper
{
public:
/**
* Memory-maps a Buffer.
*/
Mapper(Buffer &buffer)
: buf(buffer)
Mapper(Buffer &buffer, MapType maptype = MAP_WRITE_INVALIDATE)
: buffer(buffer)
{
elems = buf.map();
data = buffer.map(maptype, 0, buffer.getSize());
}
/**
* unmaps the buffer
*/
~Mapper()
{
buf.unmap();
buffer.unmap(0, buffer.getSize());
}
/**
* Get pointer to memory mapped region
*/
void *get()
{
return elems;
}
private:
Buffer &buf;
void *elems;
Buffer &buffer;
void *data;
}; // Mapper
protected:
std::vector<DataMember> dataMembers;
size_t arrayLength;
size_t arrayStride;
// The size of the buffer, in bytes.
size_t size;
// The type of the buffer object.
BufferType type;
// Bit flags describing how the buffer can be used.
BufferUsageFlags usageFlags;
// Usage hint. GL_[DYNAMIC, STATIC, STREAM]_DRAW.
vertex::Usage usage;
uint32 map_flags;
BufferDataUsage dataUsage;
bool is_mapped;
bool mapped;
MapType mappedType;
bool immutable;
}; // Buffer
-235
View File
@@ -1,235 +0,0 @@
/**
* Copyright (c) 2006-2023 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 "Canvas.h"
#include "Graphics.h"
namespace love
{
namespace graphics
{
love::Type Canvas::type("Canvas", &Texture::type);
int Canvas::canvasCount = 0;
Canvas::Canvas(const Settings &settings)
: Texture(settings.type)
{
this->settings = settings;
width = settings.width;
height = settings.height;
pixelWidth = (int) ((width * settings.dpiScale) + 0.5);
pixelHeight = (int) ((height * settings.dpiScale) + 0.5);
format = settings.format;
if (texType == TEXTURE_VOLUME)
depth = settings.layers;
else if (texType == TEXTURE_2D_ARRAY)
layers = settings.layers;
if (width <= 0 || height <= 0 || layers <= 0)
throw love::Exception("Canvas dimensions must be greater than 0.");
if (texType != TEXTURE_2D && settings.msaa > 1)
throw love::Exception("MSAA is only supported for Canvases with the 2D texture type.");
if (settings.readable.hasValue)
readable = settings.readable.value;
else
readable = !isPixelFormatDepthStencil(format);
if (readable && isPixelFormatDepthStencil(format) && settings.msaa > 1)
throw love::Exception("Readable depth/stencil Canvases with MSAA are not currently supported.");
if ((!readable || settings.msaa > 1) && settings.mipmaps != MIPMAPS_NONE)
throw love::Exception("Non-readable and MSAA textures cannot have mipmaps.");
if (settings.mipmaps != MIPMAPS_NONE)
{
mipmapCount = getTotalMipmapCount(pixelWidth, pixelHeight, depth);
filter.mipmap = defaultMipmapFilter;
}
if (settings.mipmaps == MIPMAPS_AUTO && isPixelFormatDepthStencil(format))
throw love::Exception("Automatic mipmap generation cannot be used for depth/stencil Canvases.");
auto gfx = Module::getInstance<Graphics>(Module::M_GRAPHICS);
const Graphics::Capabilities &caps = gfx->getCapabilities();
if (!gfx->isCanvasFormatSupported(format, readable))
{
const char *fstr = "rgba8";
const char *readablestr = "";
if (readable != !isPixelFormatDepthStencil(format))
readablestr = readable ? " readable" : " non-readable";
love::getConstant(format, fstr);
throw love::Exception("The %s%s canvas format is not supported by your graphics drivers.", fstr, readablestr);
}
if (getRequestedMSAA() > 1 && texType != TEXTURE_2D)
throw love::Exception("MSAA is only supported for 2D texture types.");
if (!readable && texType != TEXTURE_2D)
throw love::Exception("Non-readable pixel formats are only supported for 2D texture types.");
if (!caps.textureTypes[texType])
{
const char *textypestr = "unknown";
Texture::getConstant(texType, textypestr);
throw love::Exception("%s textures are not supported on this system!", textypestr);
}
validateDimensions(true);
canvasCount++;
}
Canvas::~Canvas()
{
canvasCount--;
}
Canvas::MipmapMode Canvas::getMipmapMode() const
{
return settings.mipmaps;
}
int Canvas::getRequestedMSAA() const
{
return settings.msaa;
}
love::image::ImageData *Canvas::newImageData(love::image::Image *module, int slice, int mipmap, const Rect &r)
{
if (!isReadable())
throw love::Exception("Canvas:newImageData cannot be called on non-readable Canvases.");
if (isPixelFormatDepthStencil(getPixelFormat()))
throw love::Exception("Canvas:newImageData cannot be called on Canvases 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->isCanvasActive(this))
throw love::Exception("Canvas:newImageData cannot be called while that Canvas is currently active.");
PixelFormat dataformat = getPixelFormat();
if (dataformat == PIXELFORMAT_sRGBA8)
dataformat = PIXELFORMAT_RGBA8;
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);
}
return module->newImageData(r.w, r.h, dataformat);
}
void Canvas::draw(Graphics *gfx, Quad *q, const Matrix4 &t)
{
if (gfx->isCanvasActive(this))
throw love::Exception("Cannot render a Canvas to itself!");
Texture::draw(gfx, q, t);
}
void Canvas::drawLayer(Graphics *gfx, int layer, Quad *quad, const Matrix4 &m)
{
if (gfx->isCanvasActive(this, layer))
throw love::Exception("Cannot render a Canvas to itself!");
Texture::drawLayer(gfx, layer, quad, m);
}
bool Canvas::getConstant(const char *in, MipmapMode &out)
{
return mipmapModes.find(in, out);
}
bool Canvas::getConstant(MipmapMode in, const char *&out)
{
return mipmapModes.find(in, out);
}
std::vector<std::string> Canvas::getConstants(MipmapMode)
{
return mipmapModes.getNames();
}
bool Canvas::getConstant(const char *in, SettingType &out)
{
return settingTypes.find(in, out);
}
bool Canvas::getConstant(SettingType in, const char *&out)
{
return settingTypes.find(in, out);
}
const char *Canvas::getConstant(SettingType in)
{
const char *name = nullptr;
getConstant(in, name);
return name;
}
std::vector<std::string> Canvas::getConstants(SettingType)
{
return settingTypes.getNames();
}
StringMap<Canvas::MipmapMode, Canvas::MIPMAPS_MAX_ENUM>::Entry Canvas::mipmapEntries[] =
{
{ "none", MIPMAPS_NONE },
{ "manual", MIPMAPS_MANUAL },
{ "auto", MIPMAPS_AUTO },
};
StringMap<Canvas::MipmapMode, Canvas::MIPMAPS_MAX_ENUM> Canvas::mipmapModes(Canvas::mipmapEntries, sizeof(Canvas::mipmapEntries));
StringMap<Canvas::SettingType, Canvas::SETTING_MAX_ENUM>::Entry Canvas::settingTypeEntries[] =
{
// Width / height / layers are currently omittted because they're separate
// arguments to newCanvas in the wrapper code.
{ "mipmaps", SETTING_MIPMAPS },
{ "format", SETTING_FORMAT },
{ "type", SETTING_TYPE },
{ "dpiscale", SETTING_DPI_SCALE },
{ "msaa", SETTING_MSAA },
{ "readable", SETTING_READABLE },
};
StringMap<Canvas::SettingType, Canvas::SETTING_MAX_ENUM> Canvas::settingTypes(Canvas::settingTypeEntries, sizeof(Canvas::settingTypeEntries));
} // graphics
} // love
-118
View File
@@ -1,118 +0,0 @@
/**
* Copyright (c) 2006-2023 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
#include "image/Image.h"
#include "image/ImageData.h"
#include "Texture.h"
#include "common/Optional.h"
#include "common/StringMap.h"
namespace love
{
namespace graphics
{
class Graphics;
class Canvas : public Texture
{
public:
static love::Type type;
enum MipmapMode
{
MIPMAPS_NONE,
MIPMAPS_MANUAL,
MIPMAPS_AUTO,
MIPMAPS_MAX_ENUM
};
enum SettingType
{
SETTING_WIDTH,
SETTING_HEIGHT,
SETTING_LAYERS,
SETTING_MIPMAPS,
SETTING_FORMAT,
SETTING_TYPE,
SETTING_DPI_SCALE,
SETTING_MSAA,
SETTING_READABLE,
SETTING_MAX_ENUM
};
struct Settings
{
int width = 1;
int height = 1;
int layers = 1; // depth for 3D textures
MipmapMode mipmaps = MIPMAPS_NONE;
PixelFormat format = PIXELFORMAT_NORMAL;
TextureType type = TEXTURE_2D;
float dpiScale = 1.0f;
int msaa = 0;
OptionalBool readable;
};
Canvas(const Settings &settings);
virtual ~Canvas();
MipmapMode getMipmapMode() const;
int getRequestedMSAA() const;
virtual love::image::ImageData *newImageData(love::image::Image *module, int slice, int mipmap, const Rect &rect);
virtual void generateMipmaps() = 0;
virtual int getMSAA() const = 0;
virtual ptrdiff_t getRenderTargetHandle() const = 0;
void draw(Graphics *gfx, Quad *q, const Matrix4 &t) override;
void drawLayer(Graphics *gfx, int layer, Quad *q, const Matrix4 &t) override;
static int canvasCount;
static bool getConstant(const char *in, MipmapMode &out);
static bool getConstant(MipmapMode in, const char *&out);
static std::vector<std::string> getConstants(MipmapMode);
static bool getConstant(const char *in, SettingType &out);
static bool getConstant(SettingType in, const char *&out);
static const char *getConstant(SettingType in);
static std::vector<std::string> getConstants(SettingType);
protected:
Settings settings;
private:
static StringMap<MipmapMode, MIPMAPS_MAX_ENUM>::Entry mipmapEntries[];
static StringMap<MipmapMode, MIPMAPS_MAX_ENUM> mipmapModes;
static StringMap<SettingType, SETTING_MAX_ENUM>::Entry settingTypeEntries[];
static StringMap<SettingType, SETTING_MAX_ENUM> settingTypes;
}; // Canvas
} // graphics
} // love
+2 -2
View File
@@ -82,7 +82,7 @@ void Deprecations::draw(Graphics *gfx)
font.set(gfx->newDefaultFont(9, hinting), Acquire::NORETAIN);
}
gfx->flushStreamDraws();
gfx->flushBatchedDraws();
gfx->push(Graphics::STACK_ALL);
gfx->reset();
@@ -90,7 +90,7 @@ void Deprecations::draw(Graphics *gfx)
int maxcount = 4;
int remaining = std::max(0, total - maxcount);
std::vector<Font::ColoredString> strings;
std::vector<font::ColoredString> strings;
Colorf white(1, 1, 1, 1);
// Grab the newest deprecation notices first.
File diff suppressed because it is too large Load Diff
+23 -61
View File
@@ -33,7 +33,8 @@
#include "common/Vector.h"
#include "font/Rasterizer.h"
#include "Image.h"
#include "font/TextShaper.h"
#include "Texture.h"
#include "vertex.h"
#include "Volatile.h"
@@ -51,9 +52,9 @@ public:
static love::Type type;
typedef std::vector<uint32> Codepoints;
typedef vertex::XYf_STus_RGBAub GlyphVertex;
typedef XYf_STus_RGBAub GlyphVertex;
static const vertex::CommonFormat vertexFormat;
static const CommonFormat vertexFormat;
enum AlignMode
{
@@ -64,30 +65,6 @@ public:
ALIGN_MAX_ENUM
};
struct ColoredString
{
std::string str;
Colorf color;
};
struct IndexedColor
{
Colorf color;
int index;
};
struct ColoredCodepoints
{
std::vector<uint32> cps;
std::vector<IndexedColor> colors;
};
struct TextInfo
{
int width;
int height;
};
// Used to determine when to change textures in the generated vertex array.
struct DrawCommand
{
@@ -96,24 +73,21 @@ public:
int vertexcount;
};
Font(love::font::Rasterizer *r, const Texture::Filter &filter);
Font(love::font::Rasterizer *r, const SamplerState &samplerState);
virtual ~Font();
std::vector<DrawCommand> generateVertices(const ColoredCodepoints &codepoints, const Colorf &constantColor, std::vector<GlyphVertex> &vertices,
float extra_spacing = 0.0f, Vector2 offset = {}, TextInfo *info = nullptr);
std::vector<DrawCommand> generateVertices(const love::font::ColoredCodepoints &codepoints, Range range, const Colorf &constantColor, std::vector<GlyphVertex> &vertices,
float extra_spacing = 0.0f, Vector2 offset = {}, love::font::TextShaper::TextInfo *info = nullptr);
std::vector<DrawCommand> generateVerticesFormatted(const ColoredCodepoints &text, const Colorf &constantColor, float wrap, AlignMode align,
std::vector<GlyphVertex> &vertices, TextInfo *info = nullptr);
static void getCodepointsFromString(const std::string &str, Codepoints &codepoints);
static void getCodepointsFromString(const std::vector<ColoredString> &strs, ColoredCodepoints &codepoints);
std::vector<DrawCommand> generateVerticesFormatted(const love::font::ColoredCodepoints &text, const Colorf &constantColor, float wrap, AlignMode align,
std::vector<GlyphVertex> &vertices, love::font::TextShaper::TextInfo *info = nullptr);
/**
* Draws the specified text.
**/
void print(graphics::Graphics *gfx, const std::vector<ColoredString> &text, const Matrix4 &m, const Colorf &constantColor);
void printf(graphics::Graphics *gfx, const std::vector<ColoredString> &text, float wrap, AlignMode align, const Matrix4 &m, const Colorf &constantColor);
void print(graphics::Graphics *gfx, const std::vector<love::font::ColoredString> &text, const Matrix4 &m, const Colorf &constantColor);
void printf(graphics::Graphics *gfx, const std::vector<love::font::ColoredString> &text, float wrap, AlignMode align, const Matrix4 &m, const Colorf &constantColor);
/**
* Returns the height of the font.
@@ -141,8 +115,8 @@ public:
* @param max_width Optional output of the maximum width
* Returns a vector with the lines.
**/
void getWrap(const std::vector<ColoredString> &text, float wraplimit, std::vector<std::string> &lines, std::vector<int> *line_widths = nullptr);
void getWrap(const ColoredCodepoints &codepoints, float wraplimit, std::vector<ColoredCodepoints> &lines, std::vector<int> *line_widths = nullptr);
void getWrap(const std::vector<love::font::ColoredString> &text, float wraplimit, std::vector<std::string> &lines, std::vector<int> *line_widths = nullptr);
void getWrap(const love::font::ColoredCodepoints &codepoints, float wraplimit, std::vector<Range> &ranges, std::vector<int> *line_widths = nullptr);
/**
* Sets the line height (which should be a number to multiply the font size by,
@@ -156,8 +130,8 @@ public:
**/
float getLineHeight() const;
void setFilter(const Texture::Filter &f);
const Texture::Filter &getFilter() const;
void setSamplerState(const SamplerState &s);
const SamplerState &getSamplerState() const;
// Extra font metrics
int getAscent() const;
@@ -191,7 +165,6 @@ private:
struct Glyph
{
Texture *texture;
int spacing;
GlyphVertex vertices[4];
};
@@ -204,38 +177,30 @@ private:
void createTexture();
TextureSize getNextTextureSize() const;
love::font::GlyphData *getRasterizerGlyphData(uint32 glyph, float &dpiscale);
const Glyph &addGlyph(uint32 glyph);
const Glyph &findGlyph(uint32 glyph);
love::font::GlyphData *getRasterizerGlyphData(love::font::TextShaper::GlyphIndex glyphindex, float &dpiscale);
const Glyph &addGlyph(love::font::TextShaper::GlyphIndex glyphindex);
const Glyph &findGlyph(love::font::TextShaper::GlyphIndex glyphindex);
void printv(Graphics *gfx, const Matrix4 &t, const std::vector<DrawCommand> &drawcommands, const std::vector<GlyphVertex> &vertices);
std::vector<StrongRef<love::font::Rasterizer>> rasterizers;
int height;
float lineHeight;
StrongRef<love::font::TextShaper> shaper;
int textureWidth;
int textureHeight;
std::vector<StrongRef<love::graphics::Image>> images;
std::vector<StrongRef<Texture>> textures;
// maps glyphs to glyph texture information
std::unordered_map<uint32, Glyph> glyphs;
// map of left/right glyph pairs to horizontal kerning.
std::unordered_map<uint64, float> kerning;
// maps packed glyph index values to glyph texture information
std::unordered_map<uint64, Glyph> glyphs;
PixelFormat pixelFormat;
Texture::Filter filter;
SamplerState samplerState;
float dpiScale;
int textureX, textureY;
int rowHeight;
bool useSpacesAsTab;
// ID which is incremented when the texture cache is invalidated.
uint32 textureCacheID;
@@ -244,9 +209,6 @@ private:
// use, for edge antialiasing.
static const int TEXTURE_PADDING = 2;
// This will be used if the Rasterizer doesn't have a tab character itself.
static const int SPACES_PER_TAB = 4;
static StringMap<AlignMode, ALIGN_MAX_ENUM>::Entry alignModeEntries[];
static StringMap<AlignMode, ALIGN_MAX_ENUM> alignModes;
File diff suppressed because it is too large Load Diff
+255 -259
View File
@@ -32,15 +32,14 @@
#include "StreamBuffer.h"
#include "vertex.h"
#include "Texture.h"
#include "Canvas.h"
#include "Font.h"
#include "ShaderStage.h"
#include "Shader.h"
#include "Quad.h"
#include "Mesh.h"
#include "Image.h"
#include "GraphicsReadback.h"
#include "Deprecations.h"
#include "depthstencil.h"
#include "renderstate.h"
#include "math/Transform.h"
#include "font/Rasterizer.h"
#include "font/Font.h"
@@ -59,14 +58,23 @@ namespace graphics
class SpriteBatch;
class ParticleSystem;
class Text;
class TextBatch;
class Video;
class Buffer;
typedef Optional<Colorf> OptionalColorf;
typedef Optional<ColorD> OptionalColorD;
const int MAX_COLOR_RENDER_TARGETS = 8;
enum Renderer
{
RENDERER_NONE,
RENDERER_OPENGL,
RENDERER_METAL,
RENDERER_VULKAN,
RENDERER_MAX_ENUM
};
/**
* Globally sets whether gamma correction is enabled. Ideally this should be set
* prior to using any Graphics module function.
@@ -97,6 +105,10 @@ Colorf unGammaCorrectColor(const Colorf &c);
bool isDebugEnabled();
const std::vector<Renderer> &getDefaultRenderers();
const std::vector<Renderer> &getRenderers();
void setRenderers(const std::vector<Renderer> &renderers);
class Graphics : public Module
{
public:
@@ -118,27 +130,6 @@ public:
ARC_MAX_ENUM
};
enum BlendMode
{
BLEND_ALPHA,
BLEND_ADD,
BLEND_SUBTRACT,
BLEND_MULTIPLY,
BLEND_LIGHTEN,
BLEND_DARKEN,
BLEND_SCREEN,
BLEND_REPLACE,
BLEND_NONE,
BLEND_MAX_ENUM
};
enum BlendAlpha
{
BLENDALPHA_MULTIPLY,
BLENDALPHA_PREMULTIPLIED,
BLENDALPHA_MAX_ENUM
};
enum LineStyle
{
LINE_ROUGH,
@@ -156,24 +147,28 @@ public:
enum Feature
{
FEATURE_MULTI_CANVAS_FORMATS,
FEATURE_MULTI_RENDER_TARGET_FORMATS,
FEATURE_CLAMP_ZERO,
FEATURE_LIGHTEN,
FEATURE_CLAMP_ONE,
FEATURE_BLEND_MINMAX,
FEATURE_LIGHTEN, // Deprecated
FEATURE_FULL_NPOT,
FEATURE_PIXEL_SHADER_HIGHP,
FEATURE_SHADER_DERIVATIVES,
FEATURE_GLSL3,
FEATURE_GLSL4,
FEATURE_INSTANCING,
FEATURE_TEXEL_BUFFER,
FEATURE_INDEX_BUFFER_32BIT,
FEATURE_COPY_BUFFER,
FEATURE_COPY_BUFFER_TO_TEXTURE,
FEATURE_COPY_TEXTURE_TO_BUFFER,
FEATURE_COPY_RENDER_TARGET_TO_BUFFER,
FEATURE_MIPMAP_RANGE,
FEATURE_INDIRECT_DRAW,
FEATURE_MAX_ENUM
};
enum Renderer
{
RENDERER_OPENGL = 0,
RENDERER_OPENGLES,
RENDERER_MAX_ENUM
};
enum SystemLimit
{
LIMIT_POINT_SIZE,
@@ -181,8 +176,13 @@ public:
LIMIT_VOLUME_TEXTURE_SIZE,
LIMIT_CUBE_TEXTURE_SIZE,
LIMIT_TEXTURE_LAYERS,
LIMIT_MULTI_CANVAS,
LIMIT_CANVAS_MSAA,
LIMIT_TEXEL_BUFFER_SIZE,
LIMIT_SHADER_STORAGE_BUFFER_SIZE,
LIMIT_THREADGROUPS_X,
LIMIT_THREADGROUPS_Y,
LIMIT_THREADGROUPS_Z,
LIMIT_RENDER_TARGETS,
LIMIT_TEXTURE_MSAA,
LIMIT_ANISOTROPY,
LIMIT_MAX_ENUM
};
@@ -200,6 +200,13 @@ public:
TEMPORARY_RT_STENCIL = (1 << 1),
};
enum IndirectArgsType
{
INDIRECT_ARGS_DISPATCH,
INDIRECT_ARGS_DRAW_VERTICES,
INDIRECT_ARGS_DRAW_INDICES,
};
struct Capabilities
{
double limits[LIMIT_MAX_ENUM];
@@ -219,54 +226,33 @@ public:
{
int drawCalls;
int drawCallsBatched;
int canvasSwitches;
int renderTargetSwitches;
int shaderSwitches;
int canvases;
int images;
int textures;
int fonts;
int64 textureMemory;
};
struct ColorMask
{
bool r, g, b, a;
ColorMask()
: r(true), g(true), b(true), a(true)
{}
ColorMask(bool _r, bool _g, bool _b, bool _a)
: r(_r), g(_g), b(_b), a(_a)
{}
bool operator == (const ColorMask &m) const
{
return r == m.r && g == m.g && b == m.b && a == m.a;
}
bool operator != (const ColorMask &m) const
{
return !(operator == (m));
}
};
struct DrawCommand
{
PrimitiveType primitiveType = PRIMITIVE_TRIANGLES;
const vertex::Attributes *attributes;
const vertex::BufferBindings *buffers;
const VertexAttributes *attributes;
const BufferBindings *buffers;
int vertexStart = 0;
int vertexCount = 0;
int instanceCount = 1;
Buffer *indirectBuffer = nullptr;
size_t indirectBufferOffset = 0;
Texture *texture = nullptr;
// TODO: This should be moved out to a state transition API?
CullMode cullMode = CULL_NONE;
DrawCommand(const vertex::Attributes *attribs, const vertex::BufferBindings *buffers)
DrawCommand(const VertexAttributes *attribs, const BufferBindings *buffers)
: attributes(attribs)
, buffers(buffers)
{}
@@ -276,8 +262,8 @@ public:
{
PrimitiveType primitiveType = PRIMITIVE_TRIANGLES;
const vertex::Attributes *attributes;
const vertex::BufferBindings *buffers;
const VertexAttributes *attributes;
const BufferBindings *buffers;
int indexCount = 0;
int instanceCount = 1;
@@ -286,35 +272,38 @@ public:
Resource *indexBuffer;
size_t indexBufferOffset = 0;
Buffer *indirectBuffer = nullptr;
size_t indirectBufferOffset = 0;
Texture *texture = nullptr;
// TODO: This should be moved out to a state transition API?
CullMode cullMode = CULL_NONE;
DrawIndexedCommand(const vertex::Attributes *attribs, const vertex::BufferBindings *buffers, Resource *indexbuffer)
DrawIndexedCommand(const VertexAttributes *attribs, const BufferBindings *buffers, Resource *indexbuffer)
: attributes(attribs)
, buffers(buffers)
, indexBuffer(indexbuffer)
{}
};
struct StreamDrawCommand
struct BatchedDrawCommand
{
PrimitiveType primitiveMode = PRIMITIVE_TRIANGLES;
vertex::CommonFormat formats[2];
vertex::TriangleIndexMode indexMode = vertex::TriangleIndexMode::NONE;
CommonFormat formats[2];
TriangleIndexMode indexMode = TRIANGLEINDEX_NONE;
int vertexCount = 0;
Texture *texture = nullptr;
Shader::StandardShader standardShaderType = Shader::STANDARD_DEFAULT;
StreamDrawCommand()
BatchedDrawCommand()
{
// VS2013 can't initialize arrays in the above manner...
formats[1] = formats[0] = vertex::CommonFormat::NONE;
formats[1] = formats[0] = CommonFormat::NONE;
}
};
struct StreamVertexData
struct BatchedVertexData
{
void *stream[2];
};
@@ -358,53 +347,53 @@ public:
struct RenderTarget
{
Canvas *canvas;
Texture *texture;
int slice;
int mipmap;
RenderTarget(Canvas *canvas, int slice = 0, int mipmap = 0)
: canvas(canvas)
RenderTarget(Texture *texture, int slice = 0, int mipmap = 0)
: texture(texture)
, slice(slice)
, mipmap(mipmap)
{}
RenderTarget()
: canvas(nullptr)
: texture(nullptr)
, slice(0)
, mipmap(0)
{}
bool operator != (const RenderTarget &other) const
{
return canvas != other.canvas || slice != other.slice || mipmap != other.mipmap;
return texture != other.texture || slice != other.slice || mipmap != other.mipmap;
}
bool operator != (const RenderTargetStrongRef &other) const
{
return canvas != other.canvas.get() || slice != other.slice || mipmap != other.mipmap;
return texture != other.texture.get() || slice != other.slice || mipmap != other.mipmap;
}
};
struct RenderTargetStrongRef
{
StrongRef<Canvas> canvas;
StrongRef<Texture> texture;
int slice = 0;
int mipmap = 0;
RenderTargetStrongRef(Canvas *canvas, int slice = 0, int mipmap = 0)
: canvas(canvas)
RenderTargetStrongRef(Texture *texture, int slice = 0, int mipmap = 0)
: texture(texture)
, slice(slice)
, mipmap(mipmap)
{}
bool operator != (const RenderTargetStrongRef &other) const
{
return canvas.get() != other.canvas.get() || slice != other.slice || mipmap != other.mipmap;
return texture.get() != other.texture.get() || slice != other.slice || mipmap != other.mipmap;
}
bool operator != (const RenderTarget &other) const
{
return canvas.get() != other.canvas || slice != other.slice || mipmap != other.mipmap;
return texture.get() != other.texture || slice != other.slice || mipmap != other.mipmap;
}
};
@@ -460,51 +449,49 @@ public:
}
};
struct DefaultShaderCode
{
std::string source[ShaderStage::STAGE_MAX_ENUM];
};
Graphics();
virtual ~Graphics();
// Implements Module.
virtual ModuleType getModuleType() const { return M_GRAPHICS; }
virtual Image *newImage(const Image::Slices &data, const Image::Settings &settings) = 0;
virtual Image *newImage(TextureType textype, PixelFormat format, int width, int height, int slices, const Image::Settings &settings) = 0;
virtual Texture *newTexture(const Texture::Settings &settings, const Texture::Slices *data = nullptr) = 0;
Quad *newQuad(Quad::Viewport v, double sw, double sh);
Font *newFont(love::font::Rasterizer *data, const Texture::Filter &filter = Texture::defaultFilter);
Font *newDefaultFont(int size, font::TrueTypeRasterizer::Hinting hinting, const Texture::Filter &filter = Texture::defaultFilter);
Font *newFont(love::font::Rasterizer *data);
Font *newDefaultFont(int size, font::TrueTypeRasterizer::Hinting hinting);
Video *newVideo(love::video::VideoStream *stream, float dpiscale);
SpriteBatch *newSpriteBatch(Texture *texture, int size, vertex::Usage usage);
SpriteBatch *newSpriteBatch(Texture *texture, int size, BufferDataUsage usage);
ParticleSystem *newParticleSystem(Texture *texture, int size);
virtual Canvas *newCanvas(const Canvas::Settings &settings) = 0;
Shader *newShader(const std::vector<std::string> &stagessource, const Shader::CompileOptions &options);
Shader *newComputeShader(const std::string &source, const Shader::CompileOptions &options);
ShaderStage *newShaderStage(ShaderStage::StageType stage, const std::string &source);
Shader *newShader(const std::string &vertex, const std::string &pixel);
virtual Buffer *newBuffer(const Buffer::Settings &settings, const std::vector<Buffer::DataDeclaration> &format, const void *data, size_t size, size_t arraylength) = 0;
virtual Buffer *newBuffer(const Buffer::Settings &settings, DataFormat format, const void *data, size_t size, size_t arraylength);
virtual Buffer *newBuffer(size_t size, const void *data, BufferType type, vertex::Usage usage, uint32 mapflags) = 0;
Mesh *newMesh(const std::vector<Buffer::DataDeclaration> &vertexformat, int vertexcount, PrimitiveType drawmode, BufferDataUsage usage);
Mesh *newMesh(const std::vector<Buffer::DataDeclaration> &vertexformat, const void *data, size_t datasize, PrimitiveType drawmode, BufferDataUsage usage);
Mesh *newMesh(const std::vector<Mesh::BufferAttribute> &attributes, PrimitiveType drawmode);
Mesh *newMesh(const std::vector<Vertex> &vertices, PrimitiveType drawmode, vertex::Usage usage);
Mesh *newMesh(int vertexcount, PrimitiveType drawmode, vertex::Usage usage);
Mesh *newMesh(const std::vector<Mesh::AttribFormat> &vertexformat, int vertexcount, PrimitiveType drawmode, vertex::Usage usage);
Mesh *newMesh(const std::vector<Mesh::AttribFormat> &vertexformat, const void *data, size_t datasize, PrimitiveType drawmode, vertex::Usage usage);
TextBatch *newTextBatch(Font *font, const std::vector<love::font::ColoredString> &text = {});
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);
bool validateShader(bool gles, const std::string &vertex, const std::string &pixel, std::string &err);
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);
/**
* Resets the current color, background color, line style, and so forth.
**/
void reset();
virtual void clear(OptionalColorf color, OptionalInt stencil, OptionalDouble depth) = 0;
virtual void clear(const std::vector<OptionalColorf> &colors, OptionalInt stencil, OptionalDouble depth) = 0;
virtual void clear(OptionalColorD color, OptionalInt stencil, OptionalDouble depth) = 0;
virtual void clear(const std::vector<OptionalColorD> &colors, OptionalInt stencil, OptionalDouble depth) = 0;
virtual void discard(const std::vector<bool> &colorbuffers, bool depthstencil) = 0;
@@ -523,7 +510,7 @@ public:
* @param width The viewport width.
* @param height The viewport height.
**/
virtual bool setMode(int width, int height, int pixelwidth, int pixelheight, bool windowhasstencil) = 0;
virtual bool setMode(void *context, int width, int height, int pixelwidth, int pixelheight, bool windowhasstencil, int msaa) = 0;
/**
* Un-sets the current graphics display mode (uninitializing objects if
@@ -558,6 +545,12 @@ public:
double getCurrentDPIScale() const;
double getScreenDPIScale() const;
virtual int getRequestedBackbufferMSAA() const = 0;
virtual int getBackbufferMSAA() const = 0;
Buffer *getQuadIndexBuffer() const { return quadIndexBuffer; }
Buffer *getFanIndexBuffer() const { return fanIndexBuffer; }
/**
* Sets the current constant color.
**/
@@ -586,15 +579,15 @@ public:
Shader *getShader() const;
void setCanvas(RenderTarget rt, uint32 temporaryRTFlags);
void setCanvas(const RenderTargets &rts);
void setCanvas(const RenderTargetsStrongRef &rts);
void setCanvas();
void setRenderTarget(RenderTarget rt, uint32 temporaryRTFlags);
void setRenderTargets(const RenderTargets &rts);
void setRenderTargets(const RenderTargetsStrongRef &rts);
void setRenderTarget();
RenderTargets getCanvas() const;
bool isCanvasActive() const;
bool isCanvasActive(Canvas *canvas) const;
bool isCanvasActive(Canvas *canvas, int slice) const;
RenderTargets getRenderTargets() const;
bool isRenderTargetActive() const;
bool isRenderTargetActive(Texture *texture) const;
bool isRenderTargetActive(Texture *texture, int slice) const;
/**
* Scissor defines a box such that everything outside that box is discarded
@@ -615,19 +608,9 @@ public:
*/
bool getScissor(Rect &rect) const;
/**
* Enables or disables drawing to the stencil buffer. When enabled, the
* color buffer is disabled.
**/
virtual void drawToStencilBuffer(StencilAction action, int value) = 0;
virtual void stopDrawToStencilBuffer() = 0;
/**
* Sets whether stencil testing is enabled.
**/
virtual void setStencilTest(CompareMode compare, int value) = 0;
void setStencilTest();
void getStencilTest(CompareMode &compare, int &value) const;
virtual void setStencilMode(StencilAction action, CompareMode compare, int value, uint32 readmask, uint32 writemask) = 0;
void setStencilMode();
void getStencilMode(StencilAction &action, CompareMode &compare, int &value, uint32 &readmask, uint32 &writemask) const;
virtual void setDepthMode(CompareMode compare, bool write) = 0;
void setDepthMode();
@@ -636,44 +619,40 @@ public:
void setMeshCullMode(CullMode cull);
CullMode getMeshCullMode() const;
virtual void setFrontFaceWinding(vertex::Winding winding) = 0;
vertex::Winding getFrontFaceWinding() const;
virtual void setFrontFaceWinding(Winding winding) = 0;
Winding getFrontFaceWinding() const;
/**
* Sets the enabled color components when rendering.
**/
virtual void setColorMask(ColorMask mask) = 0;
virtual void setColorMask(ColorChannelMask mask) = 0;
/**
* Gets the current color mask.
**/
ColorMask getColorMask() const;
ColorChannelMask getColorMask() const;
/**
* Sets the current blend mode.
**/
virtual void setBlendMode(BlendMode mode, BlendAlpha alphamode) = 0;
/**
* Gets the current blend mode.
* High-level blend mode.
**/
void setBlendMode(BlendMode mode, BlendAlpha alphamode);
BlendMode getBlendMode(BlendAlpha &alphamode) const;
/**
* Sets the default filter for images, canvases, and fonts.
* Low-level blend state.
**/
void setDefaultFilter(const Texture::Filter &f);
virtual void setBlendState(const BlendState &blend) = 0;
const BlendState &getBlendState() const;
/**
* Gets the default filter for images, canvases, and fonts.
* Sets the default sampler state for textures, videos, and fonts.
**/
const Texture::Filter &getDefaultFilter() const;
void setDefaultSamplerState(const SamplerState &s);
/**
* Default Image mipmap filter mode and sharpness values.
* Gets the default sampler state for textures, videos, and fonts.
**/
void setDefaultMipmapFilter(Texture::FilterMode filter, float sharpness);
void getDefaultMipmapFilter(Texture::FilterMode *filter, float *sharpness) const;
const SamplerState &getDefaultSamplerState() const;
/**
* Sets the line width.
@@ -720,23 +699,36 @@ public:
void captureScreenshot(const ScreenshotInfo &info);
void copyBuffer(Buffer *source, Buffer *dest, size_t sourceoffset, size_t destoffset, size_t size);
void copyTextureToBuffer(Texture *source, Buffer *dest, int slice, int mipmap, const Rect &rect, size_t destoffset, int destwidth);
void copyBufferToTexture(Buffer *source, Texture *dest, size_t sourceoffset, int sourcewidth, int slice, int mipmap, const Rect &rect);
void dispatchThreadgroups(Shader *shader, int x, int y, int z);
void dispatchIndirect(Shader *shader, Buffer *indirectargs, int argsindex);
void draw(Drawable *drawable, const Matrix4 &m);
void draw(Texture *texture, Quad *quad, const Matrix4 &m);
void drawLayer(Texture *texture, int layer, const Matrix4 &m);
void drawLayer(Texture *texture, int layer, Quad *quad, const Matrix4 &m);
void drawInstanced(Mesh *mesh, const Matrix4 &m, int instancecount);
void drawIndirect(Mesh *mesh, const Matrix4 &m, Buffer *indirectargs, int argsindex);
void drawFromShader(PrimitiveType primtype, int vertexcount, int instancecount, Texture *maintexture);
void drawFromShader(Buffer *indexbuffer, int indexcount, int instancecount, int startindex, Texture *maintexture);
void drawFromShaderIndirect(PrimitiveType primtype, Buffer *indirectargs, int argsindex, Texture *maintexture);
void drawFromShaderIndirect(Buffer *indexbuffer, Buffer *indirectargs, int argsindex, Texture *maintexture);
/**
* Draws text at the specified coordinates
**/
void print(const std::vector<Font::ColoredString> &str, const Matrix4 &m);
void print(const std::vector<Font::ColoredString> &str, Font *font, const Matrix4 &m);
void print(const std::vector<love::font::ColoredString> &str, const Matrix4 &m);
void print(const std::vector<love::font::ColoredString> &str, Font *font, const Matrix4 &m);
/**
* Draws formatted text on screen at the specified coordinates.
**/
void printf(const std::vector<Font::ColoredString> &str, float wrap, Font::AlignMode align, const Matrix4 &m);
void printf(const std::vector<Font::ColoredString> &str, Font *font, float wrap, Font::AlignMode align, const Matrix4 &m);
void printf(const std::vector<love::font::ColoredString> &str, float wrap, Font::AlignMode align, const Matrix4 &m);
void printf(const std::vector<love::font::ColoredString> &str, Font *font, float wrap, Font::AlignMode align, const Matrix4 &m);
/**
* Draws a series of points at the specified positions.
@@ -825,18 +817,25 @@ public:
const Capabilities &getCapabilities() const;
/**
* Gets whether the specified pixel format is supported by Canvases or
* Images.
* Converts PIXELFORMAT_NORMAL and PIXELFORMAT_HDR into a real format.
**/
virtual bool isCanvasFormatSupported(PixelFormat format) const = 0;
virtual bool isCanvasFormatSupported(PixelFormat format, bool readable) const = 0;
virtual bool isImageFormatSupported(PixelFormat format, bool sRGB = false) const = 0;
virtual PixelFormat getSizedFormat(PixelFormat format, bool rendertarget, bool readable) const = 0;
/**
* Gets whether the specified pixel format usage is supported.
**/
virtual bool isPixelFormatSupported(PixelFormat format, uint32 usage, bool sRGB = false) = 0;
/**
* Gets the renderer used by love.graphics.
**/
virtual Renderer getRenderer() const = 0;
/**
* Whether shaders will use GLSL ES or not (mobile shaders).
**/
virtual bool usesGLSLES() const = 0;
/**
* Returns system-dependent renderer information.
* Returned strings can vary greatly between systems! Do not rely on it for
@@ -854,7 +853,7 @@ public:
void pop();
const Matrix4 &getTransform() const;
const Matrix4 &getProjection() const;
const Matrix4 &getDeviceProjection() const;
void rotate(float r);
void scale(float x, float y = 1.0f);
@@ -862,25 +861,37 @@ public:
void shear(float kx, float ky);
void origin();
void applyTransform(love::math::Transform *transform);
void replaceTransform(love::math::Transform *transform);
void applyTransform(const Matrix4 &m);
void replaceTransform(const Matrix4 &m);
Vector2 transformPoint(Vector2 point);
Vector2 inverseTransformPoint(Vector2 point);
void setOrthoProjection(float w, float h, float near, float far);
void setPerspectiveProjection(float verticalfov, float aspect, float near, float far);
void setCustomProjection(const Matrix4 &m);
void resetProjection();
virtual Matrix4 computeDeviceProjection(const Matrix4 &projection, bool rendertotexture) const = 0;
virtual void draw(const DrawCommand &cmd) = 0;
virtual void draw(const DrawIndexedCommand &cmd) = 0;
virtual void drawQuads(int start, int count, const vertex::Attributes &attributes, const vertex::BufferBindings &buffers, Texture *texture) = 0;
virtual void drawQuads(int start, int count, const VertexAttributes &attributes, const BufferBindings &buffers, Texture *texture) = 0;
void flushStreamDraws();
StreamVertexData requestStreamDraw(const StreamDrawCommand &command);
void flushBatchedDraws();
BatchedVertexData requestBatchedDraw(const BatchedDrawCommand &command);
static void flushStreamDrawsGlobal();
static void flushBatchedDrawsGlobal();
virtual Shader::Language getShaderLanguageTarget() const = 0;
const DefaultShaderCode &getCurrentDefaultShaderCode() const;
Texture *getTemporaryTexture(PixelFormat format, int w, int h, int samples);
void releaseTemporaryTexture(Texture *texture);
void cleanupCachedShaderStage(ShaderStage::StageType type, const std::string &cachekey);
Buffer *getTemporaryBuffer(size_t size, DataFormat format, uint32 usageflags, BufferDataUsage datausage);
void releaseTemporaryBuffer(Buffer *buffer);
void cleanupCachedShaderStage(ShaderStageType type, const std::string &cachekey);
void validateIndirectArgsBuffer(IndirectArgsType argstype, Buffer *indirectargs, int argsindex);
template <typename T>
T *getScratchBuffer(size_t count)
@@ -893,52 +904,34 @@ public:
return (T *) scratchBuffer.data();
}
static bool getConstant(const char *in, DrawMode &out);
static bool getConstant(DrawMode in, const char *&out);
static std::vector<std::string> getConstants(DrawMode);
static Graphics *createInstance();
static bool getConstant(const char *in, ArcMode &out);
static bool getConstant(ArcMode in, const char *&out);
static std::vector<std::string> getConstants(ArcMode);
static bool getConstant(const char *in, BlendMode &out);
static bool getConstant(BlendMode in, const char *&out);
static std::vector<std::string> getConstants(BlendMode);
static bool getConstant(const char *in, BlendAlpha &out);
static bool getConstant(BlendAlpha in, const char *&out);
static std::vector<std::string> getConstants(BlendAlpha);
static bool getConstant(const char *in, LineStyle &out);
static bool getConstant(LineStyle in, const char *&out);
static std::vector<std::string> getConstants(LineStyle);
static bool getConstant(const char *in, LineJoin &out);
static bool getConstant(LineJoin in, const char *&out);
static std::vector<std::string> getConstants(LineJoin);
static bool getConstant(const char *in, Feature &out);
static bool getConstant(Feature in, const char *&out);
static bool getConstant(const char *in, SystemLimit &out);
static bool getConstant(SystemLimit in, const char *&out);
static bool getConstant(const char *in, StackType &out);
static bool getConstant(StackType in, const char *&out);
static std::vector<std::string> getConstants(StackType);
// Default shader code (a shader is always required internally.)
static DefaultShaderCode defaultShaderCode[Shader::STANDARD_MAX_ENUM][Shader::LANGUAGE_MAX_ENUM][2];
STRINGMAP_CLASS_DECLARE(DrawMode);
STRINGMAP_CLASS_DECLARE(ArcMode);
STRINGMAP_CLASS_DECLARE(LineStyle);
STRINGMAP_CLASS_DECLARE(LineJoin);
STRINGMAP_CLASS_DECLARE(Feature);
STRINGMAP_CLASS_DECLARE(SystemLimit);
STRINGMAP_CLASS_DECLARE(StackType);
protected:
enum DeviceProjectionFlags
{
DEVICE_PROJECTION_DEFAULT = 0,
DEVICE_PROJECTION_FLIP_Y = (1 << 0),
DEVICE_PROJECTION_Z_01 = (1 << 1),
DEVICE_PROJECTION_REVERSE_Z = (1 << 2),
};
struct DisplayState
{
DisplayState();
Colorf color = Colorf(1.0, 1.0, 1.0, 1.0);
Colorf backgroundColor = Colorf(0.0, 0.0, 0.0, 1.0);
BlendMode blendMode = BLEND_ALPHA;
BlendAlpha blendAlphaMode = BLENDALPHA_MULTIPLY;
BlendState blend = computeBlendState(BLEND_ALPHA, BLENDALPHA_MULTIPLY);
float lineWidth = 1.0f;
LineStyle lineStyle = LINE_SMOOTH;
@@ -949,37 +942,37 @@ protected:
bool scissor = false;
Rect scissorRect = Rect();
CompareMode stencilCompare = COMPARE_ALWAYS;
int stencilTestValue = 0;
StencilState stencil;
CompareMode depthTest = COMPARE_ALWAYS;
bool depthWrite = false;
CullMode meshCullMode = CULL_NONE;
vertex::Winding winding = vertex::WINDING_CCW;
Winding winding = WINDING_CCW;
StrongRef<Font> font;
StrongRef<Shader> shader;
RenderTargetsStrongRef renderTargets;
ColorMask colorMask = ColorMask(true, true, true, true);
ColorChannelMask colorMask;
bool wireframe = false;
Texture::Filter defaultFilter = Texture::Filter();
bool useCustomProjection = false;
Matrix4 customProjection;
Texture::FilterMode defaultMipmapFilter = Texture::FILTER_LINEAR;
float defaultMipmapSharpness = 0.0f;
// Default mipmap filter is set in the DisplayState constructor.
SamplerState defaultSamplerState = SamplerState();
};
struct StreamBufferState
struct BatchedDrawState
{
StreamBuffer *vb[2];
StreamBuffer *indexBuffer = nullptr;
PrimitiveType primitiveMode = PRIMITIVE_TRIANGLES;
vertex::CommonFormat formats[2];
CommonFormat formats[2];
StrongRef<Texture> texture;
Shader::StandardShader standardShaderType = Shader::STANDARD_DEFAULT;
int vertexCount = 0;
@@ -988,37 +981,61 @@ protected:
StreamBuffer::MapInfo vbMap[2];
StreamBuffer::MapInfo indexBufferMap = StreamBuffer::MapInfo();
StreamBufferState()
BatchedDrawState()
{
vb[0] = vb[1] = nullptr;
formats[0] = formats[1] = vertex::CommonFormat::NONE;
formats[0] = formats[1] = CommonFormat::NONE;
vbMap[0] = vbMap[1] = StreamBuffer::MapInfo();
}
};
struct TemporaryCanvas
struct TemporaryBuffer
{
Canvas *canvas;
Buffer *buffer;
size_t size;
int framesSinceUse;
TemporaryCanvas(Canvas *c)
: canvas(c)
, framesSinceUse(0)
TemporaryBuffer(Buffer *buf, size_t size)
: buffer(buf)
, size(size)
, framesSinceUse(-1)
{}
};
virtual ShaderStage *newShaderStageInternal(ShaderStage::StageType stage, const std::string &cachekey, const std::string &source, bool gles) = 0;
virtual Shader *newShaderInternal(ShaderStage *vertex, ShaderStage *pixel) = 0;
virtual StreamBuffer *newStreamBuffer(BufferType type, size_t size) = 0;
struct TemporaryTexture
{
Texture *texture;
int framesSinceUse;
virtual void setCanvasInternal(const RenderTargets &rts, int w, int h, int pixelw, int pixelh, bool hasSRGBcanvas) = 0;
TemporaryTexture(Texture *tex)
: texture(tex)
, framesSinceUse(-1)
{}
};
ShaderStage *newShaderStage(ShaderStageType stage, const std::string &source, const Shader::CompileOptions &options, const Shader::SourceInfo &info, bool cache);
virtual ShaderStage *newShaderStageInternal(ShaderStageType stage, const std::string &cachekey, const std::string &source, bool gles) = 0;
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(Shader *shader, int x, int y, int z) = 0;
virtual bool dispatch(Shader *shader, Buffer *indirectargs, size_t argsoffset) = 0;
virtual void setRenderTargetsInternal(const RenderTargets &rts, int pixelw, int pixelh, bool hasSRGBtexture) = 0;
virtual void initCapabilities() = 0;
virtual void getAPIStats(int &shaderswitches) const = 0;
void createQuadIndexBuffer();
void createFanIndexBuffer();
Canvas *getTemporaryCanvas(PixelFormat format, int w, int h, int samples);
void updateTemporaryResources();
void clearTemporaryResources();
void updatePendingReadbacks();
void restoreState(const DisplayState &s);
void restoreStateChecked(const DisplayState &s);
@@ -1027,6 +1044,9 @@ protected:
void pushIdentityTransform();
void popTransform();
void updateDeviceProjection(const Matrix4 &projection);
Matrix4 calculateDeviceProjection(const Matrix4 &projection, uint32 flags) const;
int width;
int height;
int pixelWidth;
@@ -1035,36 +1055,37 @@ protected:
bool created;
bool active;
bool writingToStencil;
StrongRef<love::graphics::Font> defaultFont;
std::vector<ScreenshotInfo> pendingScreenshotCallbacks;
std::vector<StrongRef<GraphicsReadback>> pendingReadbacks;
StreamBufferState streamBufferState;
BatchedDrawState batchedDrawState;
std::vector<Matrix4> transformStack;
Matrix4 projectionMatrix;
Matrix4 deviceProjectionMatrix;
std::vector<double> pixelScaleStack;
std::vector<DisplayState> states;
std::vector<StackType> stackTypeStack;
std::vector<TemporaryCanvas> temporaryCanvases;
std::vector<TemporaryBuffer> temporaryBuffers;
std::vector<TemporaryTexture> temporaryTextures;
int canvasSwitchCount;
int renderTargetSwitchCount;
int drawCalls;
int drawCallsBatched;
Buffer *quadIndexBuffer;
Buffer *fanIndexBuffer;
Capabilities capabilities;
Deprecations deprecations;
static const size_t MAX_USER_STACK_DEPTH = 128;
static const int MAX_TEMPORARY_CANVAS_UNUSED_FRAMES = 16;
static const int MAX_TEMPORARY_RESOURCE_UNUSED_FRAMES = 16;
private:
@@ -1073,37 +1094,12 @@ private:
std::vector<uint8> scratchBuffer;
std::unordered_map<std::string, ShaderStage *> cachedShaderStages[ShaderStage::STAGE_MAX_ENUM];
static StringMap<DrawMode, DRAW_MAX_ENUM>::Entry drawModeEntries[];
static StringMap<DrawMode, DRAW_MAX_ENUM> drawModes;
static StringMap<ArcMode, ARC_MAX_ENUM>::Entry arcModeEntries[];
static StringMap<ArcMode, ARC_MAX_ENUM> arcModes;
static StringMap<BlendMode, BLEND_MAX_ENUM>::Entry blendModeEntries[];
static StringMap<BlendMode, BLEND_MAX_ENUM> blendModes;
static StringMap<BlendAlpha, BLENDALPHA_MAX_ENUM>::Entry blendAlphaEntries[];
static StringMap<BlendAlpha, BLENDALPHA_MAX_ENUM> blendAlphaModes;
static StringMap<LineStyle, LINE_MAX_ENUM>::Entry lineStyleEntries[];
static StringMap<LineStyle, LINE_MAX_ENUM> lineStyles;
static StringMap<LineJoin, LINE_JOIN_MAX_ENUM>::Entry lineJoinEntries[];
static StringMap<LineJoin, LINE_JOIN_MAX_ENUM> lineJoins;
static StringMap<Feature, FEATURE_MAX_ENUM>::Entry featureEntries[];
static StringMap<Feature, FEATURE_MAX_ENUM> features;
static StringMap<SystemLimit, LIMIT_MAX_ENUM>::Entry systemLimitEntries[];
static StringMap<SystemLimit, LIMIT_MAX_ENUM> systemLimits;
static StringMap<StackType, STACK_MAX_ENUM>::Entry stackTypeEntries[];
static StringMap<StackType, STACK_MAX_ENUM> stackTypes;
std::unordered_map<std::string, ShaderStage *> cachedShaderStages[SHADERSTAGE_MAX_ENUM];
}; // Graphics
STRINGMAP_DECLARE(Renderer);
} // graphics
} // love
+229
View File
@@ -0,0 +1,229 @@
/**
* Copyright (c) 2006-2023 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-2023 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
-399
View File
@@ -1,399 +0,0 @@
/**
* Copyright (c) 2006-2023 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 "Image.h"
#include "Graphics.h"
// C++
#include <algorithm>
namespace love
{
namespace graphics
{
love::Type Image::type("Image", &Texture::type);
int Image::imageCount = 0;
Image::Image(const Slices &data, const Settings &settings, bool validatedata)
: Texture(data.getTextureType())
, settings(settings)
, data(data)
, mipmapsType(settings.mipmaps ? MIPMAPS_GENERATED : MIPMAPS_NONE)
, sRGB(isGammaCorrect() && !settings.linear)
, usingDefaultTexture(false)
{
if (validatedata && data.validate() == MIPMAPS_DATA)
mipmapsType = MIPMAPS_DATA;
}
Image::Image(TextureType textype, PixelFormat format, int width, int height, int slices, const Settings &settings)
: Image(Slices(textype), settings, false)
{
if (isPixelFormatCompressed(format))
throw love::Exception("This constructor is only supported for non-compressed pixel formats.");
if (textype == TEXTURE_2D_ARRAY)
layers = slices;
else if (textype == TEXTURE_VOLUME)
depth = slices;
init(format, width, height, settings);
}
Image::Image(const Slices &slices, const Settings &settings)
: Image(slices, settings, true)
{
if (texType == TEXTURE_2D_ARRAY)
this->layers = data.getSliceCount();
else if (texType == TEXTURE_VOLUME)
this->depth = data.getSliceCount();
love::image::ImageDataBase *slice = data.get(0, 0);
init(slice->getFormat(), slice->getWidth(), slice->getHeight(), settings);
}
Image::~Image()
{
--imageCount;
}
void Image::init(PixelFormat fmt, int w, int h, const Settings &settings)
{
Graphics *gfx = Module::getInstance<Graphics>(Module::M_GRAPHICS);
if (gfx != nullptr && !gfx->isImageFormatSupported(fmt, sRGB))
{
const char *str;
if (love::getConstant(fmt, str))
{
throw love::Exception("Cannot create image: "
"%s%s images are not supported on this system.", sRGB ? "sRGB " : "", str);
}
else
throw love::Exception("cannot create image: format is not supported on this system.");
}
pixelWidth = w;
pixelHeight = h;
width = (int) (pixelWidth / settings.dpiScale + 0.5);
height = (int) (pixelHeight / settings.dpiScale + 0.5);
format = fmt;
if (isCompressed() && mipmapsType == MIPMAPS_GENERATED)
mipmapsType = MIPMAPS_NONE;
mipmapCount = mipmapsType == MIPMAPS_NONE ? 1 : getTotalMipmapCount(w, h, depth);
if (mipmapCount > 1)
filter.mipmap = defaultMipmapFilter;
initQuad();
++imageCount;
}
void Image::uploadImageData(love::image::ImageDataBase *d, int level, int slice, int x, int y)
{
love::image::ImageData *id = dynamic_cast<love::image::ImageData *>(d);
love::thread::EmptyLock lock;
if (id != nullptr)
lock.setLock(id->getMutex());
Rect rect = {x, y, d->getWidth(), d->getHeight()};
uploadByteData(d->getFormat(), d->getData(), d->getSize(), level, slice, rect);
}
void Image::replacePixels(love::image::ImageDataBase *d, int slice, int mipmap, int x, int y, bool reloadmipmaps)
{
// No effect if the texture hasn't been created yet.
if (getHandle() == 0 || usingDefaultTexture)
return;
if (d->getFormat() != getPixelFormat())
throw love::Exception("Pixel formats must match.");
if (mipmap < 0 || (mipmapsType != MIPMAPS_DATA && mipmap > 0) || mipmap >= getMipmapCount())
throw love::Exception("Invalid image mipmap index %d.", mipmap + 1);
if (slice < 0 || (texType == TEXTURE_CUBE && slice >= 6)
|| (texType == TEXTURE_VOLUME && slice >= getDepth(mipmap))
|| (texType == TEXTURE_2D_ARRAY && slice >= getLayerCount()))
{
throw love::Exception("Invalid image slice index %d.", slice + 1);
}
Rect rect = {x, y, d->getWidth(), d->getHeight()};
int mipw = getPixelWidth(mipmap);
int miph = getPixelHeight(mipmap);
if (rect.x < 0 || rect.y < 0 || rect.w <= 0 || rect.h <= 0
|| (rect.x + rect.w) > mipw || (rect.y + rect.h) > miph)
{
throw love::Exception("Invalid rectangle dimensions (x=%d, y=%d, w=%d, h=%d) for %dx%d Image.", rect.x, rect.y, rect.w, rect.h, mipw, miph);
}
love::image::ImageDataBase *oldd = data.get(slice, mipmap);
if (oldd == nullptr)
throw love::Exception("Image does not store ImageData!");
Rect currect = {0, 0, oldd->getWidth(), oldd->getHeight()};
// We can only replace the internal Data (used when reloading due to setMode)
// if the dimensions match. We also don't currently support partial updates
// of compressed textures.
if (rect == currect)
data.set(slice, mipmap, d);
else if (isPixelFormatCompressed(d->getFormat()))
throw love::Exception("Compressed textures only support replacing the entire Image.");
Graphics::flushStreamDrawsGlobal();
uploadImageData(d, mipmap, slice, x, y);
if (reloadmipmaps && mipmap == 0 && getMipmapCount() > 1)
generateMipmaps();
}
void Image::replacePixels(const void *data, size_t size, int slice, int mipmap, const Rect &rect, bool reloadmipmaps)
{
Graphics::flushStreamDrawsGlobal();
uploadByteData(format, data, size, mipmap, slice, rect);
if (reloadmipmaps && mipmap == 0 && getMipmapCount() > 1)
generateMipmaps();
}
bool Image::isCompressed() const
{
return isPixelFormatCompressed(format);
}
bool Image::isFormatLinear() const
{
return isGammaCorrect() && !sRGB;
}
Image::MipmapsType Image::getMipmapsType() const
{
return mipmapsType;
}
Image::Slices::Slices(TextureType textype)
: textureType(textype)
{
}
void Image::Slices::clear()
{
data.clear();
}
void Image::Slices::set(int slice, int mipmap, love::image::ImageDataBase *d)
{
if (textureType == TEXTURE_VOLUME)
{
if (mipmap >= (int) data.size())
data.resize(mipmap + 1);
if (slice >= (int) data[mipmap].size())
data[mipmap].resize(slice + 1);
data[mipmap][slice].set(d);
}
else
{
if (slice >= (int) data.size())
data.resize(slice + 1);
if (mipmap >= (int) data[slice].size())
data[slice].resize(mipmap + 1);
data[slice][mipmap].set(d);
}
}
love::image::ImageDataBase *Image::Slices::get(int slice, int mipmap) const
{
if (slice < 0 || slice >= getSliceCount(mipmap))
return nullptr;
if (mipmap < 0 || mipmap >= getMipmapCount(slice))
return nullptr;
if (textureType == TEXTURE_VOLUME)
return data[mipmap][slice].get();
else
return data[slice][mipmap].get();
}
void Image::Slices::add(love::image::CompressedImageData *cdata, int startslice, int startmip, bool addallslices, bool addallmips)
{
int slicecount = addallslices ? cdata->getSliceCount() : 1;
int mipcount = addallmips ? cdata->getMipmapCount() : 1;
for (int mip = 0; mip < mipcount; mip++)
{
for (int slice = 0; slice < slicecount; slice++)
set(startslice + slice, startmip + mip, cdata->getSlice(slice, mip));
}
}
int Image::Slices::getSliceCount(int mip) const
{
if (textureType == TEXTURE_VOLUME)
{
if (mip < 0 || mip >= (int) data.size())
return 0;
return (int) data[mip].size();
}
else
return (int) data.size();
}
int Image::Slices::getMipmapCount(int slice) const
{
if (textureType == TEXTURE_VOLUME)
return (int) data.size();
else
{
if (slice < 0 || slice >= (int) data.size())
return 0;
return data[slice].size();
}
}
Image::MipmapsType Image::Slices::validate() const
{
int slicecount = getSliceCount();
int mipcount = getMipmapCount(0);
if (slicecount == 0 || mipcount == 0)
throw love::Exception("At least one ImageData or CompressedImageData is required!");
if (textureType == TEXTURE_CUBE && slicecount != 6)
throw love::Exception("Cube textures must have exactly 6 sides.");
image::ImageDataBase *firstdata = get(0, 0);
int w = firstdata->getWidth();
int h = firstdata->getHeight();
int depth = textureType == TEXTURE_VOLUME ? slicecount : 1;
PixelFormat format = firstdata->getFormat();
int expectedmips = Texture::getTotalMipmapCount(w, h, depth);
if (mipcount != expectedmips && mipcount != 1)
throw love::Exception("Image does not have all required mipmap levels (expected %d, got %d)", expectedmips, mipcount);
if (textureType == TEXTURE_CUBE && w != h)
throw love::Exception("Cube images must have equal widths and heights for each cube face.");
int mipw = w;
int miph = h;
int mipslices = slicecount;
for (int mip = 0; mip < mipcount; mip++)
{
if (textureType == TEXTURE_VOLUME)
{
slicecount = getSliceCount(mip);
if (slicecount != mipslices)
throw love::Exception("Invalid number of image data layers in mipmap level %d (expected %d, got %d)", mip+1, mipslices, slicecount);
}
for (int slice = 0; slice < slicecount; slice++)
{
auto slicedata = get(slice, mip);
if (slicedata == nullptr)
throw love::Exception("Missing image data (slice %d, mipmap level %d)", slice+1, mip+1);
int realw = slicedata->getWidth();
int realh = slicedata->getHeight();
if (getMipmapCount(slice) != mipcount)
throw love::Exception("All Image layers must have the same mipmap count.");
if (mipw != realw)
throw love::Exception("Width of image data (slice %d, mipmap level %d) is incorrect (expected %d, got %d)", slice+1, mip+1, mipw, realw);
if (miph != realh)
throw love::Exception("Height of image data (slice %d, mipmap level %d) is incorrect (expected %d, got %d)", slice+1, mip+1, miph, realh);
if (format != slicedata->getFormat())
throw love::Exception("All Image slices and mipmaps must have the same pixel format.");
}
mipw = std::max(mipw / 2, 1);
miph = std::max(miph / 2, 1);
if (textureType == TEXTURE_VOLUME)
mipslices = std::max(mipslices / 2, 1);
}
if (mipcount > 1)
return MIPMAPS_DATA;
else
return MIPMAPS_NONE;
}
bool Image::getConstant(const char *in, SettingType &out)
{
return settingTypes.find(in, out);
}
bool Image::getConstant(SettingType in, const char *&out)
{
return settingTypes.find(in, out);
}
const char *Image::getConstant(SettingType in)
{
const char *name = nullptr;
getConstant(in, name);
return name;
}
std::vector<std::string> Image::getConstants(SettingType)
{
return settingTypes.getNames();
}
StringMap<Image::SettingType, Image::SETTING_MAX_ENUM>::Entry Image::settingTypeEntries[] =
{
{ "mipmaps", SETTING_MIPMAPS },
{ "linear", SETTING_LINEAR },
{ "dpiscale", SETTING_DPI_SCALE },
};
StringMap<Image::SettingType, Image::SETTING_MAX_ENUM> Image::settingTypes(Image::settingTypeEntries, sizeof(Image::settingTypeEntries));
} // graphics
} // love
-144
View File
@@ -1,144 +0,0 @@
/**
* Copyright (c) 2006-2023 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/StringMap.h"
#include "common/math.h"
#include "image/ImageData.h"
#include "image/CompressedImageData.h"
#include "Texture.h"
namespace love
{
namespace graphics
{
class Image : public Texture
{
public:
static love::Type type;
enum MipmapsType
{
MIPMAPS_NONE,
MIPMAPS_DATA,
MIPMAPS_GENERATED,
};
enum SettingType
{
SETTING_MIPMAPS,
SETTING_LINEAR,
SETTING_DPI_SCALE,
SETTING_MAX_ENUM
};
struct Settings
{
bool mipmaps = false;
bool linear = false;
float dpiScale = 1.0f;
};
struct Slices
{
public:
Slices(TextureType textype);
void clear();
void set(int slice, int mipmap, love::image::ImageDataBase *data);
love::image::ImageDataBase *get(int slice, int mipmap) const;
void add(love::image::CompressedImageData *cdata, int startslice, int startmip, bool addallslices, bool addallmips);
int getSliceCount(int mip = 0) const;
int getMipmapCount(int slice = 0) const;
MipmapsType validate() const;
TextureType getTextureType() const { return textureType; }
private:
TextureType textureType;
// For 2D/Cube/2DArray texture types, each element in the data array has
// an array of mipmap levels. For 3D texture types, each mipmap level
// has an array of layers.
std::vector<std::vector<StrongRef<love::image::ImageDataBase>>> data;
}; // Slices
virtual ~Image();
void replacePixels(love::image::ImageDataBase *d, int slice, int mipmap, int x, int y, bool reloadmipmaps);
void replacePixels(const void *data, size_t size, int slice, int mipmap, const Rect &rect, bool reloadmipmaps);
bool isFormatLinear() const;
bool isCompressed() const;
MipmapsType getMipmapsType() const;
static int imageCount;
static bool getConstant(const char *in, SettingType &out);
static bool getConstant(SettingType in, const char *&out);
static const char *getConstant(SettingType in);
static std::vector<std::string> getConstants(SettingType);
protected:
Image(const Slices &data, const Settings &settings);
Image(TextureType textype, PixelFormat format, int width, int height, int slices, const Settings &settings);
void uploadImageData(love::image::ImageDataBase *d, int level, int slice, int x, int y);
virtual void uploadByteData(PixelFormat pixelformat, const void *data, size_t size, int level, int slice, const Rect &r) = 0;
virtual void generateMipmaps() = 0;
// The settings used to initialize this Image.
Settings settings;
Slices data;
MipmapsType mipmapsType;
bool sRGB;
// True if the image wasn't able to be properly created and it had to fall
// back to a default texture.
bool usingDefaultTexture;
private:
Image(const Slices &data, const Settings &settings, bool validatedata);
void init(PixelFormat fmt, int w, int h, const Settings &settings);
static StringMap<SettingType, SETTING_MAX_ENUM>::Entry settingTypeEntries[];
static StringMap<SettingType, SETTING_MAX_ENUM> settingTypes;
}; // Image
} // graphics
} // love
+325 -322
View File
@@ -34,215 +34,140 @@ namespace love
namespace graphics
{
static const char *getBuiltinAttribName(BuiltinVertexAttribute attribid)
{
const char *name = "";
vertex::getConstant(attribid, name);
return name;
}
static_assert(offsetof(Vertex, x) == sizeof(float) * 0, "Incorrect position offset in Vertex struct");
static_assert(offsetof(Vertex, s) == sizeof(float) * 2, "Incorrect texture coordinate offset in Vertex struct");
static_assert(offsetof(Vertex, color.r) == sizeof(float) * 4, "Incorrect color offset in Vertex struct");
std::vector<Mesh::AttribFormat> Mesh::getDefaultVertexFormat()
std::vector<Buffer::DataDeclaration> Mesh::getDefaultVertexFormat()
{
// Corresponds to the love::Vertex struct.
std::vector<Mesh::AttribFormat> vertexformat = {
{ getBuiltinAttribName(ATTRIB_POS), vertex::DATA_FLOAT, 2 },
{ getBuiltinAttribName(ATTRIB_TEXCOORD), vertex::DATA_FLOAT, 2 },
{ getBuiltinAttribName(ATTRIB_COLOR), vertex::DATA_UNORM8, 4 },
};
return vertexformat;
return Buffer::getCommonFormatDeclaration(CommonFormat::XYf_STf_RGBAub);
}
love::Type Mesh::type("Mesh", &Drawable::type);
Mesh::Mesh(graphics::Graphics *gfx, const std::vector<AttribFormat> &vertexformat, const void *data, size_t datasize, PrimitiveType drawmode, vertex::Usage usage)
: vertexFormat(vertexformat)
, vertexBuffer(nullptr)
, vertexCount(0)
, vertexStride(0)
, indexBuffer(nullptr)
, useIndexBuffer(false)
, indexCount(0)
, indexDataType(INDEX_UINT16)
, primitiveType(drawmode)
, rangeStart(-1)
, rangeCount(-1)
Mesh::Mesh(graphics::Graphics *gfx, const std::vector<Buffer::DataDeclaration> &vertexformat, const void *data, size_t datasize, PrimitiveType drawmode, BufferDataUsage usage)
: primitiveType(drawmode)
{
try
{
vertexData = new uint8[datasize];
}
catch (std::exception &)
{
throw love::Exception("Out of memory");
}
memcpy(vertexData, data, datasize);
Buffer::Settings settings(BUFFERUSAGEFLAG_VERTEX, usage);
vertexBuffer.set(gfx->newBuffer(settings, vertexformat, vertexData, datasize, 0), Acquire::NORETAIN);
vertexCount = vertexBuffer->getArrayLength();
vertexStride = vertexBuffer->getArrayStride();
vertexFormat = vertexBuffer->getDataMembers();
setupAttachedAttributes();
calculateAttributeSizes();
vertexCount = datasize / vertexStride;
indexDataType = vertex::getIndexDataTypeFromMax(vertexCount);
if (vertexCount == 0)
throw love::Exception("Data size is too small for specified vertex attribute formats.");
vertexBuffer = gfx->newBuffer(datasize, data, BUFFER_VERTEX, usage, Buffer::MAP_EXPLICIT_RANGE_MODIFY | Buffer::MAP_READ);
vertexScratchBuffer = new char[vertexStride];
indexDataType = getIndexDataTypeFromMax(vertexCount);
}
Mesh::Mesh(graphics::Graphics *gfx, const std::vector<AttribFormat> &vertexformat, int vertexcount, PrimitiveType drawmode, vertex::Usage usage)
: vertexFormat(vertexformat)
, vertexBuffer(nullptr)
, vertexCount((size_t) vertexcount)
, vertexStride(0)
, indexBuffer(nullptr)
, useIndexBuffer(false)
, indexCount(0)
, indexDataType(vertex::getIndexDataTypeFromMax(vertexcount))
Mesh::Mesh(graphics::Graphics *gfx, const std::vector<Buffer::DataDeclaration> &vertexformat, int vertexcount, PrimitiveType drawmode, BufferDataUsage usage)
: vertexCount((size_t) vertexcount)
, indexDataType(getIndexDataTypeFromMax(vertexcount))
, primitiveType(drawmode)
, rangeStart(-1)
, rangeCount(-1)
{
if (vertexcount <= 0)
throw love::Exception("Invalid number of vertices (%d).", vertexcount);
Buffer::Settings settings(BUFFERUSAGEFLAG_VERTEX, usage);
vertexBuffer.set(gfx->newBuffer(settings, vertexformat, nullptr, 0, vertexcount), Acquire::NORETAIN);
vertexStride = vertexBuffer->getArrayStride();
vertexFormat = vertexBuffer->getDataMembers();
setupAttachedAttributes();
calculateAttributeSizes();
size_t buffersize = vertexCount * vertexStride;
try
{
vertexData = new uint8[vertexBuffer->getSize()];
}
catch (std::exception &)
{
throw love::Exception("Out of memory");
}
vertexBuffer = gfx->newBuffer(buffersize, nullptr, BUFFER_VERTEX, usage, Buffer::MAP_EXPLICIT_RANGE_MODIFY | Buffer::MAP_READ);
memset(vertexData, 0, vertexBuffer->getSize());
vertexBuffer->fill(0, vertexBuffer->getSize(), vertexData);
}
// Initialize the buffer's contents to 0.
memset(vertexBuffer->map(), 0, buffersize);
vertexBuffer->setMappedRangeModified(0, vertexBuffer->getSize());
vertexBuffer->unmap();
Mesh::Mesh(const std::vector<Mesh::BufferAttribute> &attributes, PrimitiveType drawmode)
: primitiveType(drawmode)
{
if (attributes.size() == 0)
throw love::Exception("At least one buffer attribute must be specified in this constructor.");
vertexScratchBuffer = new char[vertexStride];
attachedAttributes = attributes;
vertexCount = attachedAttributes.size() > 0 ? LOVE_UINT32_MAX : 0;
for (const auto &attrib : attachedAttributes)
{
if ((attrib.buffer->getUsageFlags() & BUFFERUSAGEFLAG_VERTEX) == 0)
throw love::Exception("Buffer must be created with vertex buffer support to be used as a Mesh vertex attribute.");
if (getAttachedAttributeIndex(attrib.name) != -1)
throw love::Exception("Duplicate vertex attribute name: %s", attrib.name.c_str());
vertexCount = std::min(vertexCount, attrib.buffer->getArrayLength());
}
indexDataType = getIndexDataTypeFromMax(vertexCount);
}
Mesh::~Mesh()
{
delete vertexBuffer;
delete indexBuffer;
delete[] vertexScratchBuffer;
for (const auto &attrib : attachedAttributes)
{
if (attrib.second.mesh != this)
attrib.second.mesh->release();
}
delete vertexData;
if (indexData != nullptr)
free(indexData);
}
void Mesh::setupAttachedAttributes()
{
for (size_t i = 0; i < vertexFormat.size(); i++)
{
const std::string &name = vertexFormat[i].name;
const std::string &name = vertexFormat[i].decl.name;
if (attachedAttributes.find(name) != attachedAttributes.end())
if (getAttachedAttributeIndex(name) != -1)
throw love::Exception("Duplicate vertex attribute name: %s", name.c_str());
attachedAttributes[name] = {this, (int) i, STEP_PER_VERTEX, true};
attachedAttributes.push_back({name, vertexBuffer, nullptr, (int) i, 0, STEP_PER_VERTEX, true});
}
}
void Mesh::calculateAttributeSizes()
int Mesh::getAttachedAttributeIndex(const std::string &name) const
{
size_t stride = 0;
for (const AttribFormat &format : vertexFormat)
for (int i = 0; i < (int) attachedAttributes.size(); i++)
{
size_t size = vertex::getDataTypeSize(format.type) * format.components;
if (format.components <= 0 || format.components > 4)
throw love::Exception("Vertex attributes must have between 1 and 4 components.");
// Hardware really doesn't like attributes that aren't 32 bit-aligned.
if (size % 4 != 0)
throw love::Exception("Vertex attributes must have enough components to be a multiple of 32 bits.");
// Total size in bytes of each attribute in a single vertex.
attributeSizes.push_back(size);
stride += size;
if (attachedAttributes[i].name == name)
return i;
}
vertexStride = stride;
return -1;
}
size_t Mesh::getAttributeOffset(size_t attribindex) const
{
size_t offset = 0;
for (size_t i = 0; i < attribindex; i++)
offset += attributeSizes[i];
return offset;
}
void Mesh::setVertex(size_t vertindex, const void *data, size_t datasize)
void *Mesh::checkVertexDataOffset(size_t vertindex, size_t *byteoffset)
{
if (vertindex >= vertexCount)
throw love::Exception("Invalid vertex index: %ld", vertindex + 1);
if (vertexData == nullptr)
throw love::Exception("Mesh must own its own vertex buffer.");
size_t offset = vertindex * vertexStride;
size_t size = std::min(datasize, vertexStride);
uint8 *bufferdata = (uint8 *) vertexBuffer->map();
memcpy(bufferdata + offset, data, size);
vertexBuffer->setMappedRangeModified(offset, size);
}
size_t Mesh::getVertex(size_t vertindex, void *data, size_t datasize)
{
if (vertindex >= vertexCount)
throw love::Exception("Invalid vertex index: %ld", vertindex + 1);
size_t offset = vertindex * vertexStride;
size_t size = std::min(datasize, vertexStride);
// We're relying on map() returning read/write data... ew.
const uint8 *bufferdata = (const uint8 *) vertexBuffer->map();
memcpy(data, bufferdata + offset, size);
return size;
}
void *Mesh::getVertexScratchBuffer()
{
return vertexScratchBuffer;
}
void Mesh::setVertexAttribute(size_t vertindex, int attribindex, const void *data, size_t datasize)
{
if (vertindex >= vertexCount)
throw love::Exception("Invalid vertex index: %ld", vertindex + 1);
if (attribindex >= (int) vertexFormat.size())
throw love::Exception("Invalid vertex attribute index: %d", attribindex + 1);
size_t offset = vertindex * vertexStride + getAttributeOffset(attribindex);
size_t size = std::min(datasize, attributeSizes[attribindex]);
uint8 *bufferdata = (uint8 *) vertexBuffer->map();
memcpy(bufferdata + offset, data, size);
vertexBuffer->setMappedRangeModified(offset, size);
}
size_t Mesh::getVertexAttribute(size_t vertindex, int attribindex, void *data, size_t datasize)
{
if (vertindex >= vertexCount)
throw love::Exception("Invalid vertex index: %ld", vertindex + 1);
if (attribindex >= (int) vertexFormat.size())
throw love::Exception("Invalid vertex attribute index: %d", attribindex + 1);
size_t offset = vertindex * vertexStride + getAttributeOffset(attribindex);
size_t size = std::min(datasize, attributeSizes[attribindex]);
// We're relying on map() returning read/write data... ew.
const uint8 *bufferdata = (const uint8 *) vertexBuffer->map();
memcpy(data, bufferdata + offset, size);
return size;
if (byteoffset != nullptr)
*byteoffset = offset;
return vertexData + offset;
}
size_t Mesh::getVertexCount() const
@@ -255,138 +180,134 @@ size_t Mesh::getVertexStride() const
return vertexStride;
}
const std::vector<Mesh::AttribFormat> &Mesh::getVertexFormat() const
Buffer *Mesh::getVertexBuffer() const
{
return vertexBuffer;
}
const std::vector<Buffer::DataMember> &Mesh::getVertexFormat() const
{
return vertexFormat;
}
vertex::DataType Mesh::getAttributeInfo(int attribindex, int &components) const
{
if (attribindex < 0 || attribindex >= (int) vertexFormat.size())
throw love::Exception("Invalid vertex attribute index: %d", attribindex + 1);
components = vertexFormat[attribindex].components;
return vertexFormat[attribindex].type;
}
int Mesh::getAttributeIndex(const std::string &name) const
{
for (int i = 0; i < (int) vertexFormat.size(); i++)
{
if (vertexFormat[i].name == name)
return i;
}
return -1;
}
void Mesh::setAttributeEnabled(const std::string &name, bool enable)
{
auto it = attachedAttributes.find(name);
if (it == attachedAttributes.end())
int index = getAttachedAttributeIndex(name);
if (index == -1)
throw love::Exception("Mesh does not have an attached vertex attribute named '%s'", name.c_str());
it->second.enabled = enable;
attachedAttributes[index].enabled = enable;
}
bool Mesh::isAttributeEnabled(const std::string &name) const
{
const auto it = attachedAttributes.find(name);
if (it == attachedAttributes.end())
int index = getAttachedAttributeIndex(name);
if (index == -1)
throw love::Exception("Mesh does not have an attached vertex attribute named '%s'", name.c_str());
return it->second.enabled;
return attachedAttributes[index].enabled;
}
void Mesh::attachAttribute(const std::string &name, Mesh *mesh, const std::string &attachname, AttributeStep step)
void Mesh::attachAttribute(const std::string &name, Buffer *buffer, Mesh *mesh, const std::string &attachname, int startindex, AttributeStep step)
{
if ((buffer->getUsageFlags() & BUFFERUSAGEFLAG_VERTEX) == 0)
throw love::Exception("Buffer must be created with vertex buffer support to be used as a Mesh vertex attribute.");
auto gfx = Module::getInstance<Graphics>(Module::M_GRAPHICS);
if (step == STEP_PER_INSTANCE && !gfx->getCapabilities().features[Graphics::FEATURE_INSTANCING])
throw love::Exception("Vertex attribute instancing is not supported on this system.");
if (mesh != this)
{
for (const auto &it : mesh->attachedAttributes)
{
// If the supplied Mesh has attached attributes of its own, then we
// prevent it from being attached to avoid reference cycles.
if (it.second.mesh != mesh)
throw love::Exception("Cannot attach a Mesh which has attached Meshes of its own.");
}
}
if (startindex < 0 || startindex >= (int) buffer->getArrayLength())
throw love::Exception("Invalid start array index %d.", startindex + 1);
AttachedAttribute oldattrib = {};
AttachedAttribute newattrib = {};
BufferAttribute oldattrib = {};
BufferAttribute newattrib = {};
auto it = attachedAttributes.find(name);
if (it != attachedAttributes.end())
oldattrib = it->second;
else if (attachedAttributes.size() + 1 > vertex::Attributes::MAX)
throw love::Exception("A maximum of %d attributes can be attached at once.", vertex::Attributes::MAX);
int oldindex = getAttachedAttributeIndex(name);
if (oldindex != -1)
oldattrib = attachedAttributes[oldindex];
else if (attachedAttributes.size() + 1 > VertexAttributes::MAX)
throw love::Exception("A maximum of %d attributes can be attached at once.", VertexAttributes::MAX);
newattrib.name = name;
newattrib.buffer = buffer;
newattrib.mesh = mesh;
newattrib.enabled = oldattrib.mesh ? oldattrib.enabled : true;
newattrib.index = mesh->getAttributeIndex(attachname);
newattrib.enabled = oldattrib.buffer.get() ? oldattrib.enabled : true;
newattrib.indexInBuffer = buffer->getDataMemberIndex(attachname);
newattrib.startArrayIndex = startindex;
newattrib.step = step;
if (newattrib.index < 0)
throw love::Exception("The specified mesh does not have a vertex attribute named '%s'", attachname.c_str());
if (newattrib.indexInBuffer < 0)
throw love::Exception("The specified vertex buffer does not have a vertex attribute named '%s'", attachname.c_str());
if (newattrib.mesh != this)
newattrib.mesh->retain();
attachedAttributes[name] = newattrib;
if (oldattrib.mesh && oldattrib.mesh != this)
oldattrib.mesh->release();
if (oldindex != -1)
attachedAttributes[oldindex] = newattrib;
else
attachedAttributes.push_back(newattrib);
}
bool Mesh::detachAttribute(const std::string &name)
{
auto it = attachedAttributes.find(name);
int index = getAttachedAttributeIndex(name);
if (index == -1)
return false;
if (it != attachedAttributes.end() && it->second.mesh != this)
{
it->second.mesh->release();
attachedAttributes.erase(it);
attachedAttributes.erase(attachedAttributes.begin() + index);
if (getAttributeIndex(name) != -1)
attachAttribute(name, this, name);
if (vertexBuffer.get() && vertexBuffer->getDataMemberIndex(name) != -1)
attachAttribute(name, vertexBuffer, nullptr, name);
return true;
}
return false;
return true;
}
void *Mesh::mapVertexData()
const std::vector<Mesh::BufferAttribute> &Mesh::getAttachedAttributes() const
{
return vertexBuffer->map();
return attachedAttributes;
}
void Mesh::unmapVertexData(size_t modifiedoffset, size_t modifiedsize)
void *Mesh::getVertexData() const
{
vertexBuffer->setMappedRangeModified(modifiedoffset, modifiedsize);
vertexBuffer->unmap();
return vertexData;
}
void Mesh::setVertexDataModified(size_t offset, size_t size)
{
if (vertexData != nullptr)
modifiedVertexData.encapsulate(offset, size);
}
void Mesh::flush()
{
vertexBuffer->unmap();
if (vertexBuffer.get() && vertexData != nullptr && modifiedVertexData.isValid())
{
if (vertexBuffer->getDataUsage() == BUFFERDATAUSAGE_STREAM)
{
vertexBuffer->fill(0, vertexBuffer->getSize(), vertexData);
}
else
{
size_t offset = modifiedVertexData.getOffset();
size_t size = modifiedVertexData.getSize();
vertexBuffer->fill(offset, size, vertexData + offset);
}
if (indexBuffer != nullptr)
indexBuffer->unmap();
modifiedVertexData.invalidate();
}
if (indexDataModified && indexData != nullptr && indexBuffer != nullptr)
{
indexBuffer->fill(0, indexBuffer->getSize(), indexData);
indexDataModified = false;
}
}
/**
* Copies index data from a vector to a mapped index buffer.
**/
template <typename T>
static void copyToIndexBuffer(const std::vector<uint32> &indices, Buffer::Mapper &buffermap, size_t maxval)
static void copyToIndexBuffer(const std::vector<uint32> &indices, void *data, size_t maxval)
{
T *elems = (T *) buffermap.get();
T *elems = (T *) data;
for (size_t i = 0; i < indices.size(); i++)
{
@@ -401,70 +322,83 @@ void Mesh::setVertexMap(const std::vector<uint32> &map)
{
size_t maxval = getVertexCount();
IndexDataType datatype = vertex::getIndexDataTypeFromMax(maxval);
IndexDataType datatype = getIndexDataTypeFromMax(maxval);
DataFormat dataformat = getIndexDataFormat(datatype);
// Calculate the size in bytes of the index buffer data.
size_t size = map.size() * vertex::getIndexDataSize(datatype);
size_t size = map.size() * getIndexDataSize(datatype);
if (indexBuffer && size > indexBuffer->getSize())
{
delete indexBuffer;
indexBuffer = nullptr;
}
bool recreate = indexData == nullptr || indexBuffer.get() == nullptr
|| size > indexBuffer->getSize() || indexBuffer->getDataMember(0).decl.format != dataformat;
if (!indexBuffer && size > 0)
if (recreate)
{
auto gfx = Module::getInstance<graphics::Graphics>(Module::M_GRAPHICS);
indexBuffer = gfx->newBuffer(size, nullptr, BUFFER_INDEX, vertexBuffer->getUsage(), Buffer::MAP_READ);
auto usage = vertexBuffer.get() ? vertexBuffer->getDataUsage() : BUFFERDATAUSAGE_DYNAMIC;
Buffer::Settings settings(BUFFERUSAGEFLAG_INDEX, usage);
auto buffer = StrongRef<Buffer>(gfx->newBuffer(settings, dataformat, nullptr, size, 0), Acquire::NORETAIN);
auto data = (uint8 *) realloc(indexData, size);
if (data == nullptr)
throw love::Exception("Out of memory.");
indexData = data;
indexBuffer = buffer;
}
useIndexBuffer = true;
indexCount = map.size();
useIndexBuffer = true;
indexDataType = datatype;
if (!indexBuffer || indexCount == 0)
if (indexCount == 0)
return;
Buffer::Mapper ibomap(*indexBuffer);
// Fill the buffer with the index values from the vector.
switch (datatype)
{
case INDEX_UINT16:
copyToIndexBuffer<uint16>(map, ibomap, maxval);
copyToIndexBuffer<uint16>(map, indexData, maxval);
break;
case INDEX_UINT32:
default:
copyToIndexBuffer<uint32>(map, ibomap, maxval);
copyToIndexBuffer<uint32>(map, indexData, maxval);
break;
}
indexDataType = datatype;
indexDataModified = true;
}
void Mesh::setVertexMap(IndexDataType datatype, const void *data, size_t datasize)
{
if (indexBuffer && datasize > indexBuffer->getSize())
{
delete indexBuffer;
indexBuffer = nullptr;
}
DataFormat dataformat = getIndexDataFormat(datatype);
if (!indexBuffer && datasize > 0)
bool recreate = indexData == nullptr || indexBuffer.get() == nullptr
|| datasize > indexBuffer->getSize() || indexBuffer->getDataMember(0).decl.format != dataformat;
if (recreate)
{
auto gfx = Module::getInstance<graphics::Graphics>(Module::M_GRAPHICS);
indexBuffer = gfx->newBuffer(datasize, nullptr, BUFFER_INDEX, vertexBuffer->getUsage(), Buffer::MAP_READ);
auto usage = vertexBuffer.get() ? vertexBuffer->getDataUsage() : BUFFERDATAUSAGE_DYNAMIC;
Buffer::Settings settings(BUFFERUSAGEFLAG_INDEX, usage);
auto buffer = StrongRef<Buffer>(gfx->newBuffer(settings, dataformat, nullptr, datasize, 0), Acquire::NORETAIN);
auto data = (uint8 *) realloc(indexData, datasize);
if (data == nullptr)
throw love::Exception("Out of memory.");
indexData = data;
indexBuffer = buffer;
}
indexCount = datasize / vertex::getIndexDataSize(datatype);
if (!indexBuffer || indexCount == 0)
return;
Buffer::Mapper ibomap(*indexBuffer);
memcpy(ibomap.get(), data, datasize);
indexCount = datasize / getIndexDataSize(datatype);
useIndexBuffer = true;
indexDataType = datatype;
if (indexCount == 0)
return;
memcpy(indexData, data, datasize);
indexDataModified = true;
}
void Mesh::setVertexMap()
@@ -476,9 +410,9 @@ void Mesh::setVertexMap()
* Copies index data from a mapped buffer to a vector.
**/
template <typename T>
static void copyFromIndexBuffer(void *buffer, size_t count, std::vector<uint32> &indices)
static void copyFromIndexBuffer(const void *buffer, size_t count, std::vector<uint32> &indices)
{
T *elems = (T *) buffer;
const T *elems = (const T *) buffer;
for (size_t i = 0; i < count; i++)
indices.push_back((uint32) elems[i]);
}
@@ -489,30 +423,54 @@ bool Mesh::getVertexMap(std::vector<uint32> &map) const
return false;
map.clear();
map.reserve(indexCount);
if (!indexBuffer || indexCount == 0)
if (indexData == nullptr || indexCount == 0)
return true;
// We unmap the buffer in Mesh::draw, Mesh::setVertexMap, and Mesh::flush.
void *buffer = indexBuffer->map();
map.reserve(indexCount);
// Fill the vector from the buffer.
switch (indexDataType)
{
case INDEX_UINT16:
copyFromIndexBuffer<uint16>(buffer, indexCount, map);
copyFromIndexBuffer<uint16>(indexData, indexCount, map);
break;
case INDEX_UINT32:
default:
copyFromIndexBuffer<uint32>(buffer, indexCount, map);
copyFromIndexBuffer<uint32>(indexData, indexCount, map);
break;
}
return true;
}
size_t Mesh::getVertexMapCount() const
void Mesh::setIndexBuffer(Buffer *buffer)
{
// Buffer constructor does the rest of the validation for index buffers
// (data member formats, etc.)
if (buffer != nullptr && (buffer->getUsageFlags() & BUFFERUSAGEFLAG_INDEX) == 0)
throw love::Exception("setIndexBuffer requires a Buffer created as an index buffer.");
indexBuffer.set(buffer);
useIndexBuffer = buffer != nullptr;
indexCount = buffer != nullptr ? buffer->getArrayLength() : 0;
if (buffer != nullptr)
indexDataType = getIndexDataType(buffer->getDataMember(0).decl.format);
if (indexData != nullptr)
{
free(indexData);
indexData = nullptr;
}
}
Buffer *Mesh::getIndexBuffer() const
{
return indexBuffer;
}
size_t Mesh::getIndexCount() const
{
return indexCount;
}
@@ -547,83 +505,111 @@ void Mesh::setDrawRange(int start, int count)
if (start < 0 || count <= 0)
throw love::Exception("Invalid draw range.");
rangeStart = start;
rangeCount = count;
drawRange = Range(start, count);
}
void Mesh::setDrawRange()
{
rangeStart = rangeCount = -1;
drawRange.invalidate();
}
bool Mesh::getDrawRange(int &start, int &count) const
{
if (rangeStart < 0 || rangeCount <= 0)
if (!drawRange.isValid())
return false;
start = rangeStart;
count = rangeCount;
start = (int) drawRange.getOffset();
count = (int) drawRange.getSize();
return true;
}
void Mesh::draw(Graphics *gfx, const love::Matrix4 &m)
{
drawInstanced(gfx, m, 1);
drawInternal(gfx, m, 1, nullptr, 0);
}
void Mesh::drawInstanced(Graphics *gfx, const Matrix4 &m, int instancecount)
{
if (vertexCount <= 0 || instancecount <= 0)
drawInternal(gfx, m, instancecount, nullptr, 0);
}
void Mesh::drawIndirect(Graphics *gfx, const Matrix4 &m, Buffer *indirectargs, int argsindex)
{
drawInternal(gfx, m, 0, indirectargs, argsindex);
}
void Mesh::drawInternal(Graphics *gfx, const Matrix4 &m, int instancecount, Buffer *indirectargs, int argsindex)
{
if (vertexCount <= 0 || (instancecount <= 0 && indirectargs == nullptr))
return;
if (instancecount > 1 && !gfx->getCapabilities().features[Graphics::FEATURE_INSTANCING])
throw love::Exception("Instancing is not supported on this system.");
gfx->flushStreamDraws();
if (indirectargs != nullptr)
{
if (primitiveType == PRIMITIVE_TRIANGLE_FAN)
throw love::Exception("The fan draw mode is not supported in indirect draws.");
if (useIndexBuffer && indexBuffer != nullptr)
gfx->validateIndirectArgsBuffer(Graphics::INDIRECT_ARGS_DRAW_INDICES, indirectargs, argsindex);
else
gfx->validateIndirectArgsBuffer(Graphics::INDIRECT_ARGS_DRAW_VERTICES, indirectargs, argsindex);
}
// Some graphics backends don't natively support triangle fans. So we'd
// have to emulate them with triangles plus an index buffer... which doesn't
// work so well when there's already a custom index buffer.
if (primitiveType == PRIMITIVE_TRIANGLE_FAN && useIndexBuffer && indexBuffer != nullptr)
throw love::Exception("The 'fan' Mesh draw mode cannot be used with an index buffer / vertex map.");
gfx->flushBatchedDraws();
flush();
if (Shader::isDefaultActive())
Shader::attachDefault(Shader::STANDARD_DEFAULT);
if (Shader::current && texture.get())
Shader::current->checkMainTexture(texture);
if (Shader::current)
Shader::current->validateDrawState(primitiveType, texture);
vertex::Attributes attributes;
vertex::BufferBindings buffers;
VertexAttributes attributes;
BufferBindings buffers;
int activebuffers = 0;
for (const auto &attrib : attachedAttributes)
{
if (!attrib.second.enabled)
if (!attrib.enabled)
continue;
Mesh *mesh = attrib.second.mesh;
Buffer *buffer = attrib.buffer.get();
int attributeindex = -1;
// If the attribute is one of the LOVE-defined ones, use the constant
// attribute index for it, otherwise query the index from the shader.
BuiltinVertexAttribute builtinattrib;
if (vertex::getConstant(attrib.first.c_str(), builtinattrib))
if (getConstant(attrib.name.c_str(), builtinattrib))
attributeindex = (int) builtinattrib;
else if (Shader::current)
attributeindex = Shader::current->getVertexAttributeIndex(attrib.first);
attributeindex = Shader::current->getVertexAttributeIndex(attrib.name);
if (attributeindex >= 0)
{
// Make sure the buffer isn't mapped (sends data to GPU if needed.)
mesh->vertexBuffer->unmap();
if (attrib.mesh.get())
attrib.mesh->flush();
const auto &formats = mesh->getVertexFormat();
const auto &format = formats[attrib.second.index];
const auto &member = buffer->getDataMember(attrib.indexInBuffer);
uint16 offset = (uint16) mesh->getAttributeOffset(attrib.second.index);
uint16 stride = (uint16) mesh->getVertexStride();
uint16 offset = (uint16) member.offset;
uint16 stride = (uint16) buffer->getArrayStride();
size_t bufferoffset = (size_t) stride * attrib.startArrayIndex;
attributes.set(attributeindex, format.type, (uint8) format.components, offset, activebuffers);
attributes.setBufferLayout(activebuffers, stride, attrib.second.step);
attributes.set(attributeindex, member.decl.format, offset, activebuffers);
attributes.setBufferLayout(activebuffers, stride, attrib.step);
// TODO: Ideally we want to reuse buffers with the same stride+step.
buffers.set(activebuffers, mesh->vertexBuffer, 0);
buffers.set(activebuffers, buffer, bufferoffset);
activebuffers++;
}
}
@@ -634,12 +620,30 @@ void Mesh::drawInstanced(Graphics *gfx, const Matrix4 &m, int instancecount)
Graphics::TempTransform transform(gfx, m);
if (useIndexBuffer && indexBuffer != nullptr && indexCount > 0)
{
// Make sure the index buffer isn't mapped (sends data to GPU if needed.)
indexBuffer->unmap();
Buffer *indexbuffer = useIndexBuffer ? indexBuffer : nullptr;
int indexcount = (int) indexCount;
Range range = drawRange;
Graphics::DrawIndexedCommand cmd(&attributes, &buffers, indexBuffer);
// Emulated triangle fan via an index buffer.
if (primitiveType == PRIMITIVE_TRIANGLE_FAN && indexbuffer == nullptr && gfx->getFanIndexBuffer())
{
indexbuffer = gfx->getFanIndexBuffer();
indexcount = graphics::getIndexCount(TRIANGLEINDEX_FAN, vertexCount);
if (range.isValid())
{
int start = graphics::getIndexCount(TRIANGLEINDEX_FAN, (int) range.getOffset());
int count = graphics::getIndexCount(TRIANGLEINDEX_FAN, (int) range.getSize());
range = Range(start, count);
}
}
if (indexbuffer != nullptr && (indexcount > 0 || indirectargs != nullptr))
{
Range r(0, indexcount);
if (range.isValid())
r.intersect(range);
Graphics::DrawIndexedCommand cmd(&attributes, &buffers, indexbuffer);
cmd.primitiveType = primitiveType;
cmd.indexType = indexDataType;
@@ -647,34 +651,33 @@ void Mesh::drawInstanced(Graphics *gfx, const Matrix4 &m, int instancecount)
cmd.texture = texture;
cmd.cullMode = gfx->getMeshCullMode();
int start = std::min(std::max(0, rangeStart), (int) indexCount - 1);
cmd.indexBufferOffset = start * vertex::getIndexDataSize(indexDataType);
cmd.indexBufferOffset = r.getOffset() * indexbuffer->getArrayStride();
cmd.indexCount = (int) r.getSize();
cmd.indexCount = (int) indexCount;
if (rangeCount > 0)
cmd.indexCount = std::min(cmd.indexCount, rangeCount);
cmd.indexCount = std::min(cmd.indexCount, (int) indexCount - start);
cmd.indirectBuffer = indirectargs;
cmd.indirectBufferOffset = argsindex * (indirectargs != nullptr ? indirectargs->getArrayStride() : 0);
if (cmd.indexCount > 0)
gfx->draw(cmd);
}
else if (vertexCount > 0)
else if (vertexCount > 0 || indirectargs != nullptr)
{
Range r(0, vertexCount);
if (range.isValid())
r.intersect(range);
Graphics::DrawCommand cmd(&attributes, &buffers);
cmd.primitiveType = primitiveType;
cmd.vertexStart = std::min(std::max(0, rangeStart), (int) vertexCount - 1);
cmd.vertexCount = (int) vertexCount;
if (rangeCount > 0)
cmd.vertexCount = std::min(cmd.vertexCount, rangeCount);
cmd.vertexCount = std::min(cmd.vertexCount, (int) vertexCount - cmd.vertexStart);
cmd.vertexStart = (int) r.getOffset();
cmd.vertexCount = (int) r.getSize();
cmd.instanceCount = instancecount;
cmd.texture = texture;
cmd.cullMode = gfx->getMeshCullMode();
cmd.indirectBuffer = indirectargs;
cmd.indirectBufferOffset = argsindex * (indirectargs != nullptr ? indirectargs->getArrayStride() : 0);
if (cmd.vertexCount > 0)
gfx->draw(cmd);
}
+55 -57
View File
@@ -25,6 +25,7 @@
#include "common/int.h"
#include "common/math.h"
#include "common/StringMap.h"
#include "common/Range.h"
#include "Drawable.h"
#include "Texture.h"
#include "vertex.h"
@@ -50,36 +51,30 @@ class Mesh : public Drawable
{
public:
static love::Type type;
struct AttribFormat
struct BufferAttribute
{
std::string name;
vertex::DataType type;
int components; // max 4
StrongRef<Buffer> buffer;
StrongRef<Mesh> mesh;
int indexInBuffer;
int startArrayIndex;
AttributeStep step;
bool enabled;
};
Mesh(Graphics *gfx, const std::vector<AttribFormat> &vertexformat, const void *data, size_t datasize, PrimitiveType drawmode, vertex::Usage usage);
Mesh(Graphics *gfx, const std::vector<AttribFormat> &vertexformat, int vertexcount, PrimitiveType drawmode, vertex::Usage usage);
static love::Type type;
Mesh(Graphics *gfx, const std::vector<Buffer::DataDeclaration> &vertexformat, const void *data, size_t datasize, PrimitiveType drawmode, BufferDataUsage usage);
Mesh(Graphics *gfx, const std::vector<Buffer::DataDeclaration> &vertexformat, int vertexcount, PrimitiveType drawmode, BufferDataUsage usage);
Mesh(const std::vector<BufferAttribute> &attributes, PrimitiveType drawmode);
virtual ~Mesh();
/**
* Sets the values of all attributes at a specific vertex index in the Mesh.
* The size of the data must be less than or equal to the total size of all
* vertex attributes.
* Validates a vertex index and whether the Mesh has its own vertex buffer,
* and returns a pointer to the vertex data at the given vertex index.
**/
void setVertex(size_t vertindex, const void *data, size_t datasize);
size_t getVertex(size_t vertindex, void *data, size_t datasize);
void *getVertexScratchBuffer();
/**
* Sets the values for a single attribute at a specific vertex index in the
* Mesh. The size of the data must be less than or equal to the size of the
* attribute.
**/
void setVertexAttribute(size_t vertindex, int attribindex, const void *data, size_t datasize);
size_t getVertexAttribute(size_t vertindex, int attribindex, void *data, size_t datasize);
void *checkVertexDataOffset(size_t vertindex, size_t *byteoffset);
/**
* Gets the total number of vertices that can be used when drawing the Mesh.
@@ -92,12 +87,15 @@ public:
**/
size_t getVertexStride() const;
/**
* Gets the Buffer that holds the Mesh's vertices.
**/
Buffer *getVertexBuffer() const;
/**
* Gets the format of each vertex attribute stored in the Mesh.
**/
const std::vector<AttribFormat> &getVertexFormat() const;
vertex::DataType getAttributeInfo(int attribindex, int &components) const;
int getAttributeIndex(const std::string &name) const;
const std::vector<Buffer::DataMember> &getVertexFormat() const;
/**
* Sets whether a specific vertex attribute is used when drawing the Mesh.
@@ -106,14 +104,18 @@ public:
bool isAttributeEnabled(const std::string &name) const;
/**
* Attaches a vertex attribute from another Mesh to this one. The attribute
* will be used when drawing this Mesh.
* Attaches a vertex attribute from another vertex buffer to this Mesh. The
* attribute will be used when drawing this Mesh.
* Attributes from other Meshes should also pass in the Mesh as an argument,
* to make sure this Mesh knows to flush the passed in Mesh's data to its
* buffer when drawing.
**/
void attachAttribute(const std::string &name, Mesh *mesh, const std::string &attachname, AttributeStep step = STEP_PER_VERTEX);
void attachAttribute(const std::string &name, Buffer *buffer, Mesh *mesh, const std::string &attachname, int startindex = 0, AttributeStep step = STEP_PER_VERTEX);
bool detachAttribute(const std::string &name);
const std::vector<BufferAttribute> &getAttachedAttributes() const;
void *mapVertexData();
void unmapVertexData(size_t modifiedoffset = 0, size_t modifiedsize = -1);
void *getVertexData() const;
void setVertexDataModified(size_t offset, size_t size);
/**
* Flushes all modified data to the GPU.
@@ -136,10 +138,13 @@ public:
**/
bool getVertexMap(std::vector<uint32> &map) const;
void setIndexBuffer(Buffer *buffer);
Buffer *getIndexBuffer() const;
/**
* Gets the total number of elements in the vertex map array.
**/
size_t getVertexMapCount() const;
size_t getIndexCount() const;
/**
* Sets the texture used when drawing the Mesh.
@@ -171,49 +176,42 @@ public:
void draw(Graphics *gfx, const Matrix4 &m) override;
void drawInstanced(Graphics *gfx, const Matrix4 &m, int instancecount);
void drawIndirect(Graphics *gfx, const Matrix4 &m, Buffer *indirectargs, int argsindex);
static std::vector<AttribFormat> getDefaultVertexFormat();
static std::vector<Buffer::DataDeclaration> getDefaultVertexFormat();
private:
friend class SpriteBatch;
struct AttachedAttribute
{
Mesh *mesh;
int index;
AttributeStep step;
bool enabled;
};
void setupAttachedAttributes();
void calculateAttributeSizes();
size_t getAttributeOffset(size_t attribindex) const;
int getAttachedAttributeIndex(const std::string &name) const;
std::vector<AttribFormat> vertexFormat;
std::vector<size_t> attributeSizes;
void drawInternal(Graphics *gfx, const Matrix4 &m, int instancecount, Buffer *indirectargs, int argsindex);
std::unordered_map<std::string, AttachedAttribute> attachedAttributes;
std::vector<Buffer::DataMember> vertexFormat;
std::vector<BufferAttribute> attachedAttributes;
// Vertex buffer, for the vertex data.
Buffer *vertexBuffer;
size_t vertexCount;
size_t vertexStride;
StrongRef<Buffer> vertexBuffer;
uint8 *vertexData = nullptr;
Range modifiedVertexData = Range();
// Block of memory whose size is at least as large as a single vertex. Helps
// avoid memory allocations when using Mesh::setVertex etc.
char *vertexScratchBuffer;
size_t vertexCount = 0;
size_t vertexStride = 0;
// Index buffer, for the vertex map.
Buffer *indexBuffer;
bool useIndexBuffer;
size_t indexCount;
IndexDataType indexDataType;
StrongRef<Buffer> indexBuffer;
uint8 *indexData = nullptr;
bool indexDataModified = false;
bool useIndexBuffer = false;
size_t indexCount = 0;
IndexDataType indexDataType = INDEX_UINT16;
PrimitiveType primitiveType;
PrimitiveType primitiveType = PRIMITIVE_TRIANGLES;
int rangeStart;
int rangeCount;
Range drawRange = Range();
StrongRef<Texture> texture;
+12 -9
View File
@@ -93,7 +93,7 @@ ParticleSystem::ParticleSystem(Texture *texture, uint32 size)
, offset(float(texture->getWidth())*0.5f, float(texture->getHeight())*0.5f)
, defaultOffset(true)
, relativeRotation(false)
, vertexAttributes(vertex::CommonFormat::XYf_STf_RGBAub, 0)
, vertexAttributes(CommonFormat::XYf_STf_RGBAub, 0)
, buffer(nullptr)
{
if (size == 0 || size > MAX_PARTICLES)
@@ -191,7 +191,9 @@ void ParticleSystem::createBuffers(size_t size)
auto gfx = Module::getInstance<Graphics>(Module::M_GRAPHICS);
size_t bytes = sizeof(Vertex) * size * 4;
buffer = gfx->newBuffer(bytes, nullptr, BUFFER_VERTEX, vertex::USAGE_STREAM, 0);
Buffer::Settings settings(BUFFERUSAGEFLAG_VERTEX, BUFFERDATAUSAGE_STREAM);
auto decl = Buffer::getCommonFormatDeclaration(CommonFormat::XYf_STf_RGBAub);
buffer = gfx->newBuffer(settings, decl, nullptr, bytes, 0);
}
catch (std::bad_alloc &)
{
@@ -203,7 +205,8 @@ void ParticleSystem::createBuffers(size_t size)
void ParticleSystem::deleteBuffers()
{
delete[] pMem;
delete buffer;
if (buffer)
buffer->release();
pMem = nullptr;
buffer = nullptr;
@@ -1029,18 +1032,18 @@ void ParticleSystem::draw(Graphics *gfx, const Matrix4 &m)
if (pCount == 0 || texture.get() == nullptr || pMem == nullptr || buffer == nullptr)
return;
gfx->flushStreamDraws();
gfx->flushBatchedDraws();
if (Shader::isDefaultActive())
Shader::attachDefault(Shader::STANDARD_DEFAULT);
if (Shader::current && texture.get())
Shader::current->checkMainTexture(texture);
if (Shader::current)
Shader::current->validateDrawState(PRIMITIVE_TRIANGLES, texture);
const Vector2 *positions = texture->getQuad()->getVertexPositions();
const Vector2 *texcoords = texture->getQuad()->getVertexTexCoords();
Vertex *pVerts = (Vertex *) buffer->map();
Vertex *pVerts = (Vertex *) buffer->map(Buffer::MAP_WRITE_INVALIDATE, 0, buffer->getSize());
Particle *p = pHead;
bool useQuads = !quads.empty();
@@ -1076,11 +1079,11 @@ void ParticleSystem::draw(Graphics *gfx, const Matrix4 &m)
p = p->next;
}
buffer->unmap();
buffer->unmap(0, pCount * sizeof(Vertex) * 4);
Graphics::TempTransform transform(gfx, m);
vertex::BufferBindings vertexbuffers;
BufferBindings vertexbuffers;
vertexbuffers.set(0, buffer, 0);
gfx->drawQuads(0, pCount, vertexAttributes, vertexbuffers, texture);
+1 -1
View File
@@ -673,7 +673,7 @@ private:
bool relativeRotation;
const vertex::Attributes vertexAttributes;
const VertexAttributes vertexAttributes;
Buffer *buffer;
static StringMap<AreaSpreadDistribution, DISTRIBUTION_MAX_ENUM>::Entry distributionsEntries[];
+6 -6
View File
@@ -82,7 +82,7 @@ void Polyline::render(const Vector2 *coords, size_t count, size_t size_hint, flo
// extra degenerate triangle in between the core line and the overdraw
// line in order to break up the strip into two. This will let us draw
// everything in one draw call.
if (triangle_mode == vertex::TriangleIndexMode::STRIP)
if (triangle_mode == TRIANGLEINDEX_STRIP)
extra_vertices = 2;
}
@@ -415,20 +415,20 @@ void Polyline::draw(love::graphics::Graphics *gfx)
int maxvertices = LOVE_UINT16_MAX - 3;
int advance = maxvertices;
if (triangle_mode == vertex::TriangleIndexMode::STRIP)
if (triangle_mode == TRIANGLEINDEX_STRIP)
advance -= 2;
for (int vertex_start = 0; vertex_start < total_vertex_count; vertex_start += advance)
{
const Vector2 *verts = vertices + vertex_start;
Graphics::StreamDrawCommand cmd;
cmd.formats[0] = vertex::getSinglePositionFormat(is2D);
cmd.formats[1] = vertex::CommonFormat::RGBAub;
Graphics::BatchedDrawCommand cmd;
cmd.formats[0] = getSinglePositionFormat(is2D);
cmd.formats[1] = CommonFormat::RGBAub;
cmd.indexMode = triangle_mode;
cmd.vertexCount = std::min(maxvertices, total_vertex_count - vertex_start);
Graphics::StreamVertexData data = gfx->requestStreamDraw(cmd);
Graphics::BatchedVertexData data = gfx->requestBatchedDraw(cmd);
if (is2D)
t.transformXY((Vector2 *) data.stream[0], verts, cmd.vertexCount);
+3 -3
View File
@@ -44,7 +44,7 @@ class Polyline
{
public:
Polyline(vertex::TriangleIndexMode mode = vertex::TriangleIndexMode::STRIP)
Polyline(TriangleIndexMode mode = TRIANGLEINDEX_STRIP)
: vertices(nullptr)
, overdraw(nullptr)
, vertex_count(0)
@@ -94,7 +94,7 @@ protected:
Vector2 *overdraw;
size_t vertex_count;
size_t overdraw_vertex_count;
vertex::TriangleIndexMode triangle_mode;
TriangleIndexMode triangle_mode;
size_t overdraw_vertex_start;
}; // Polyline
@@ -109,7 +109,7 @@ class NoneJoinPolyline : public Polyline
public:
NoneJoinPolyline()
: Polyline(vertex::TriangleIndexMode::QUADS)
: Polyline(TRIANGLEINDEX_QUADS)
{}
void render(const Vector2 *vertices, size_t count, float halfwidth, float pixel_size, bool draw_overdraw)
File diff suppressed because it is too large Load Diff
+119 -27
View File
@@ -33,17 +33,13 @@
#include <vector>
#include <stddef.h>
namespace glslang
{
class TShader;
}
namespace love
{
namespace graphics
{
class Graphics;
class Buffer;
// A GLSL shader
class Shader : public Object, public Resource
@@ -55,9 +51,8 @@ public:
enum Language
{
LANGUAGE_GLSL1,
LANGUAGE_ESSL1,
LANGUAGE_GLSL3,
LANGUAGE_ESSL3,
LANGUAGE_GLSL4,
LANGUAGE_MAX_ENUM
};
@@ -68,12 +63,8 @@ public:
BUILTIN_TEXTURE_VIDEO_Y,
BUILTIN_TEXTURE_VIDEO_CB,
BUILTIN_TEXTURE_VIDEO_CR,
BUILTIN_MATRIX_VIEW_FROM_LOCAL,
BUILTIN_MATRIX_CLIP_FROM_VIEW,
BUILTIN_MATRIX_CLIP_FROM_LOCAL,
BUILTIN_MATRIX_VIEW_NORMAL_FROM_LOCAL,
BUILTIN_POINT_SIZE,
BUILTIN_SCREEN_SIZE,
BUILTIN_UNIFORMS_PER_DRAW,
BUILTIN_UNIFORMS_PER_DRAW_2,
BUILTIN_MAX_ENUM
};
@@ -86,6 +77,9 @@ public:
UNIFORM_UINT,
UNIFORM_BOOL,
UNIFORM_SAMPLER,
UNIFORM_STORAGETEXTURE,
UNIFORM_TEXELBUFFER,
UNIFORM_STORAGEBUFFER,
UNIFORM_UNKNOWN,
UNIFORM_MAX_ENUM
};
@@ -95,9 +89,37 @@ public:
STANDARD_DEFAULT,
STANDARD_VIDEO,
STANDARD_ARRAY,
STANDARD_POINTS,
STANDARD_MAX_ENUM
};
enum EntryPoint
{
ENTRYPOINT_NONE,
ENTRYPOINT_HIGHLEVEL,
ENTRYPOINT_CUSTOM,
ENTRYPOINT_RAW,
};
enum Access
{
ACCESS_NONE = 0,
ACCESS_READ = (1 << 0),
ACCESS_WRITE = (1 << 1),
};
struct CompileOptions
{
std::map<std::string, std::string> defines;
};
struct SourceInfo
{
Language language;
EntryPoint stages[SHADERSTAGE_MAX_ENUM];
bool usesMRT;
};
struct MatrixSize
{
short columns;
@@ -116,8 +138,13 @@ public:
};
UniformType baseType;
DataBaseType dataBaseType;
TextureType textureType;
Access access;
bool isDepthSampler;
PixelFormat storageTextureFormat;
size_t bufferStride;
size_t bufferMemberCount;
std::string name;
union
@@ -130,18 +157,46 @@ public:
size_t dataSize;
Texture **textures;
union
{
Texture **textures;
Buffer **buffers;
};
};
union LocalUniformValue
{
float f;
int32 i;
uint32 u;
};
// The members in here must respect uniform buffer alignment/padding rules.
struct BuiltinUniformData
{
Matrix4 transformMatrix;
Matrix4 projectionMatrix;
Vector4 normalMatrix[3]; // 3x3 matrix padded to an array of 3 vector4s.
Colorf constantColor;
// Pixel shader-centric variables past this point.
Vector4 screenSizeParams;
};
// Pointer to currently active Shader.
static Shader *current;
// Pointer to the default Shader.
static Shader *standardShaders[STANDARD_MAX_ENUM];
Shader(ShaderStage *vertex, ShaderStage *pixel);
Shader(StrongRef<ShaderStage> stages[]);
virtual ~Shader();
/**
* Check whether a Shader has a stage.
**/
bool hasStage(ShaderStageType stage);
/**
* Binds this Shader's program to be used when rendering.
**/
@@ -170,6 +225,7 @@ public:
virtual void updateUniform(const UniformInfo *info, int count) = 0;
virtual void sendTextures(const UniformInfo *info, Texture **textures, int count) = 0;
virtual void sendBuffers(const UniformInfo *info, Buffer **buffers, int count) = 0;
/**
* Gets whether a uniform with the specified name exists and is actively
@@ -182,15 +238,21 @@ public:
**/
virtual void setVideoTextures(Texture *ytexture, Texture *cbtexture, Texture *crtexture) = 0;
TextureType getMainTextureType() const;
void checkMainTextureType(TextureType textype, bool isDepthSampler) const;
void checkMainTexture(Texture *texture) const;
const UniformInfo *getMainTextureInfo() const;
void validateDrawState(PrimitiveType primtype, Texture *maintexture) const;
static bool validate(ShaderStage *vertex, ShaderStage *pixel, std::string &err);
void getLocalThreadgroupSize(int *x, int *y, int *z);
static SourceInfo getSourceInfo(const std::string &src);
static std::string createShaderStageCode(Graphics *gfx, ShaderStageType stage, const std::string &code, const CompileOptions &options, const SourceInfo &info, bool gles, bool checksystemfeatures);
static bool validate(StrongRef<ShaderStage> stages[], std::string &err);
static bool initialize();
static void deinitialize();
static const std::string &getDefaultCode(StandardShader shader, ShaderStageType stage);
static bool getConstant(const char *in, Language &out);
static bool getConstant(Language in, const char *&out);
@@ -199,16 +261,46 @@ public:
protected:
StrongRef<ShaderStage> stages[ShaderStage::STAGE_MAX_ENUM];
struct BufferReflection
{
size_t stride;
size_t memberCount;
Access access;
};
private:
struct StorageTextureReflection
{
PixelFormat format;
Access access;
};
static StringMap<Language, LANGUAGE_MAX_ENUM>::Entry languageEntries[];
static StringMap<Language, LANGUAGE_MAX_ENUM> languages;
// Names for the built-in uniform variables.
static StringMap<BuiltinUniform, BUILTIN_MAX_ENUM>::Entry builtinNameEntries[];
static StringMap<BuiltinUniform, BUILTIN_MAX_ENUM> builtinNames;
struct LocalUniform
{
DataBaseType dataType;
std::vector<LocalUniformValue> initializerValues;
};
struct ValidationReflection
{
std::map<std::string, BufferReflection> storageBuffers;
std::map<std::string, StorageTextureReflection> storageTextures;
std::map<std::string, LocalUniform> localUniforms;
int localThreadgroupSize[3];
bool usesPointSize;
};
bool fillUniformReflectionData(UniformInfo &u);
static bool validateInternal(StrongRef<ShaderStage> stages[], std::string& err, ValidationReflection &reflection);
static DataBaseType getDataBaseType(PixelFormat format);
static bool isResourceBaseTypeCompatible(DataBaseType a, DataBaseType b);
static bool validateTexture(const UniformInfo *info, Texture *tex, bool internalUpdate);
static bool validateBuffer(const UniformInfo *info, Buffer *buffer, bool internalUpdate);
StrongRef<ShaderStage> stages[SHADERSTAGE_MAX_ENUM];
ValidationReflection validationReflection;
}; // Shader
+25 -12
View File
@@ -118,6 +118,7 @@ static const TBuiltInResource defaultTBuiltInResource = {
/* .maxTaskWorkGroupSizeY_NV = */ 1,
/* .maxTaskWorkGroupSizeZ_NV = */ 1,
/* .maxMeshViewCountNV = */ 4,
/* .maxDualSourceDrawBuffersEXT = */ 1,
/* .limits = */ {
/* .nonInductiveForLoops = */ 1,
/* .whileLoops = */ 1,
@@ -136,21 +137,23 @@ namespace love
namespace graphics
{
ShaderStage::ShaderStage(Graphics *gfx, StageType stage, const std::string &glsl, bool gles, const std::string &cachekey)
ShaderStage::ShaderStage(Graphics *gfx, ShaderStageType stage, const std::string &glsl, bool gles, const std::string &cachekey)
: stageType(stage)
, source(glsl)
, cacheKey(cachekey)
, glslangShader(nullptr)
, glslangValidationShader(nullptr)
{
EShLanguage glslangStage = EShLangCount;
if (stage == STAGE_VERTEX)
if (stage == SHADERSTAGE_VERTEX)
glslangStage = EShLangVertex;
else if (stage == STAGE_PIXEL)
else if (stage == SHADERSTAGE_PIXEL)
glslangStage = EShLangFragment;
else if (stage == SHADERSTAGE_COMPUTE)
glslangStage = EShLangCompute;
else
throw love::Exception("Cannot compile shader stage: unknown stage type.");
glslangShader = new glslang::TShader(glslangStage);
auto glslangShader = new glslang::TShader(glslangStage);
bool supportsGLSL3 = gfx->getCapabilities().features[Graphics::FEATURE_GLSL3];
int defaultversion = gles ? 100 : 120;
@@ -178,6 +181,8 @@ ShaderStage::ShaderStage(Graphics *gfx, StageType stage, const std::string &glsl
delete glslangShader;
throw love::Exception("%s", err.c_str());
}
glslangValidationShader = glslangShader;
}
ShaderStage::~ShaderStage()
@@ -189,26 +194,34 @@ ShaderStage::~ShaderStage()
gfx->cleanupCachedShaderStage(stageType, cacheKey);
}
delete glslangShader;
delete glslangValidationShader;
}
bool ShaderStage::getConstant(const char *in, StageType &out)
bool ShaderStage::getConstant(const char *in, ShaderStageType &out)
{
return stageNames.find(in, out);
}
bool ShaderStage::getConstant(StageType in, const char *&out)
bool ShaderStage::getConstant(ShaderStageType in, const char *&out)
{
return stageNames.find(in, out);
}
StringMap<ShaderStage::StageType, ShaderStage::STAGE_MAX_ENUM>::Entry ShaderStage::stageNameEntries[] =
const char *ShaderStage::getConstant(ShaderStageType in)
{
{ "vertex", STAGE_VERTEX },
{ "pixel", STAGE_PIXEL },
const char *name = nullptr;
getConstant(in, name);
return name;
}
StringMap<ShaderStageType, SHADERSTAGE_MAX_ENUM>::Entry ShaderStage::stageNameEntries[] =
{
{ "vertex", SHADERSTAGE_VERTEX },
{ "pixel", SHADERSTAGE_PIXEL },
{ "compute", SHADERSTAGE_COMPUTE },
};
StringMap<ShaderStage::StageType, ShaderStage::STAGE_MAX_ENUM> ShaderStage::stageNames(ShaderStage::stageNameEntries, sizeof(ShaderStage::stageNameEntries));
StringMap<ShaderStageType, SHADERSTAGE_MAX_ENUM> ShaderStage::stageNames(ShaderStage::stageNameEntries, sizeof(ShaderStage::stageNameEntries));
} // graphics
} // love
+23 -23
View File
@@ -22,7 +22,6 @@
#include "common/Object.h"
#include "common/StringMap.h"
#include "Volatile.h"
#include "Resource.h"
#include <stddef.h>
@@ -40,27 +39,32 @@ namespace graphics
class Graphics;
class ShaderStage : public love::Object, public Volatile, public Resource
// Order is used for stages array in ShaderStage.cpp
enum ShaderStageType
{
SHADERSTAGE_VERTEX,
SHADERSTAGE_PIXEL,
SHADERSTAGE_COMPUTE,
SHADERSTAGE_MAX_ENUM
};
class ShaderStage : public love::Object
{
public:
enum StageType
{
STAGE_VERTEX,
STAGE_PIXEL,
STAGE_MAX_ENUM
};
ShaderStage(Graphics *gfx, StageType stage, const std::string &glsl, bool gles, const std::string &cachekey);
ShaderStage(Graphics *gfx, ShaderStageType stage, const std::string &glsl, bool gles, const std::string &cachekey);
virtual ~ShaderStage();
StageType getStageType() const { return stageType; }
virtual ptrdiff_t getHandle() const = 0;
ShaderStageType getStageType() const { return stageType; }
const std::string &getSource() const { return source; }
const std::string &getWarnings() const { return warnings; }
glslang::TShader *getGLSLangShader() const { return glslangShader; }
glslang::TShader *getGLSLangValidationShader() const { return glslangValidationShader; }
static bool getConstant(const char *in, StageType &out);
static bool getConstant(StageType in, const char *&out);
static bool getConstant(const char *in, ShaderStageType &out);
static bool getConstant(ShaderStageType in, const char *&out);
static const char *getConstant(ShaderStageType in);
protected:
@@ -68,13 +72,13 @@ protected:
private:
StageType stageType;
ShaderStageType stageType;
std::string source;
std::string cacheKey;
glslang::TShader *glslangShader;
glslang::TShader *glslangValidationShader;
static StringMap<StageType, STAGE_MAX_ENUM>::Entry stageNameEntries[];
static StringMap<StageType, STAGE_MAX_ENUM> stageNames;
static StringMap<ShaderStageType, SHADERSTAGE_MAX_ENUM>::Entry stageNameEntries[];
static StringMap<ShaderStageType, SHADERSTAGE_MAX_ENUM> stageNames;
}; // ShaderStage
@@ -82,15 +86,11 @@ class ShaderStageForValidation final : public ShaderStage
{
public:
ShaderStageForValidation(Graphics *gfx, StageType stage, const std::string &glsl, bool gles)
ShaderStageForValidation(Graphics *gfx, ShaderStageType stage, const std::string &glsl, bool gles)
: ShaderStage(gfx, stage, glsl, gles, "")
{}
virtual ~ShaderStageForValidation() {}
ptrdiff_t getHandle() const override { return 0; }
bool loadVolatile() override { return true; }
void unloadVolatile() override { }
}; // ShaderStageForValidation
+86 -85
View File
@@ -40,13 +40,15 @@ namespace graphics
love::Type SpriteBatch::type("SpriteBatch", &Drawable::type);
SpriteBatch::SpriteBatch(Graphics *gfx, Texture *texture, int size, vertex::Usage usage)
SpriteBatch::SpriteBatch(Graphics *gfx, Texture *texture, int size, BufferDataUsage usage)
: texture(texture)
, size(size)
, next(0)
, color(255, 255, 255, 255)
, color_active(false)
, colorf(1.0f, 1.0f, 1.0f, 1.0f)
, array_buf(nullptr)
, vertex_data(nullptr)
, modified_sprites()
, range_start(-1)
, range_count(-1)
{
@@ -57,19 +59,29 @@ SpriteBatch::SpriteBatch(Graphics *gfx, Texture *texture, int size, vertex::Usag
throw love::Exception("A texture must be used when creating a SpriteBatch.");
if (texture->getTextureType() == TEXTURE_2D_ARRAY)
vertex_format = vertex::CommonFormat::XYf_STPf_RGBAub;
vertex_format = CommonFormat::XYf_STPf_RGBAub;
else
vertex_format = vertex::CommonFormat::XYf_STf_RGBAub;
vertex_format = CommonFormat::XYf_STf_RGBAub;
vertex_stride = vertex::getFormatStride(vertex_format);
vertex_stride = getFormatStride(vertex_format);
size_t vertex_size = vertex_stride * 4 * size;
array_buf = gfx->newBuffer(vertex_size, nullptr, BUFFER_VERTEX, usage, Buffer::MAP_EXPLICIT_RANGE_MODIFY);
vertex_data = (uint8 *) malloc(vertex_size);
if (vertex_data == nullptr)
throw love::Exception("Out of memory.");
memset(vertex_data, 0, vertex_size);
Buffer::Settings settings(BUFFERUSAGEFLAG_VERTEX, usage);
auto decl = Buffer::getCommonFormatDeclaration(vertex_format);
array_buf.set(gfx->newBuffer(settings, decl, nullptr, vertex_size, 0), Acquire::NORETAIN);
}
SpriteBatch::~SpriteBatch()
{
delete array_buf;
free(vertex_data);
}
int SpriteBatch::add(const Matrix4 &m, int index /*= -1*/)
@@ -79,8 +91,6 @@ int SpriteBatch::add(const Matrix4 &m, int index /*= -1*/)
int SpriteBatch::add(Quad *quad, const Matrix4 &m, int index /*= -1*/)
{
using namespace vertex;
if (vertex_format == CommonFormat::XYf_STPf_RGBAub)
return addLayer(quad->getLayer(), quad, m, index);
@@ -93,9 +103,10 @@ int SpriteBatch::add(Quad *quad, const Matrix4 &m, int index /*= -1*/)
const Vector2 *quadpositions = quad->getVertexPositions();
const Vector2 *quadtexcoords = quad->getVertexTexCoords();
// Always keep the buffer mapped when adding data (it'll be unmapped on draw.)
size_t offset = (index == -1 ? next : index) * vertex_stride * 4;
auto verts = (XYf_STf_RGBAub *) ((uint8 *) array_buf->map() + offset);
int spriteindex = (index == -1 ? next : index);
size_t offset = spriteindex * vertex_stride * 4;
auto verts = (XYf_STf_RGBAub *) (vertex_data + offset);
m.transformXY(verts, quadpositions, 4);
@@ -106,7 +117,7 @@ int SpriteBatch::add(Quad *quad, const Matrix4 &m, int index /*= -1*/)
verts[i].color = color;
}
array_buf->setMappedRangeModified(offset, vertex_stride * 4);
modified_sprites.encapsulate(spriteindex);
// Increment counter.
if (index == -1)
@@ -122,8 +133,6 @@ int SpriteBatch::addLayer(int layer, const Matrix4 &m, int index)
int SpriteBatch::addLayer(int layer, Quad *quad, const Matrix4 &m, int index)
{
using namespace vertex;
if (vertex_format != CommonFormat::XYf_STPf_RGBAub)
throw love::Exception("addLayer can only be called on a SpriteBatch that uses an Array Texture!");
@@ -139,9 +148,10 @@ int SpriteBatch::addLayer(int layer, Quad *quad, const Matrix4 &m, int index)
const Vector2 *quadpositions = quad->getVertexPositions();
const Vector2 *quadtexcoords = quad->getVertexTexCoords();
// Always keep the buffer mapped when adding data (it'll be unmapped on draw.)
size_t offset = (index == -1 ? next : index) * vertex_stride * 4;
auto verts = (XYf_STPf_RGBAub *) ((uint8 *) array_buf->map() + offset);
int spriteindex = (index == -1 ? next : index);
size_t offset = spriteindex * vertex_stride * 4;
auto verts = (XYf_STPf_RGBAub *) (vertex_data + offset);
m.transformXY(verts, quadpositions, 4);
@@ -153,7 +163,7 @@ int SpriteBatch::addLayer(int layer, Quad *quad, const Matrix4 &m, int index)
verts[i].color = color;
}
array_buf->setMappedRangeModified(offset, vertex_stride * 4);
modified_sprites.encapsulate(spriteindex);
// Increment counter.
if (index == -1)
@@ -170,13 +180,24 @@ void SpriteBatch::clear()
void SpriteBatch::flush()
{
array_buf->unmap();
if (modified_sprites.isValid())
{
size_t offset = modified_sprites.getOffset() * vertex_stride * 4;
size_t size = modified_sprites.getSize() * vertex_stride * 4;
if (array_buf->getDataUsage() == BUFFERDATAUSAGE_STREAM)
array_buf->fill(0, array_buf->getSize(), vertex_data);
else
array_buf->fill(offset, size, vertex_data + offset);
modified_sprites.invalidate();
}
}
void SpriteBatch::setTexture(Texture *newtexture)
{
if (texture->getTextureType() != newtexture->getTextureType())
throw love::Exception("Texture must have the same texture type as the SpriteBatch's previous texture.");
throw love::Exception("Texture must have the same type as the SpriteBatch's previous texture.");
texture.set(newtexture);
}
@@ -188,27 +209,17 @@ Texture *SpriteBatch::getTexture() const
void SpriteBatch::setColor(const Colorf &c)
{
color_active = true;
colorf.r = std::min(std::max(c.r, 0.0f), 1.0f);
colorf.g = std::min(std::max(c.g, 0.0f), 1.0f);
colorf.b = std::min(std::max(c.b, 0.0f), 1.0f);
colorf.a = std::min(std::max(c.a, 0.0f), 1.0f);
Colorf cclamped;
cclamped.r = std::min(std::max(c.r, 0.0f), 1.0f);
cclamped.g = std::min(std::max(c.g, 0.0f), 1.0f);
cclamped.b = std::min(std::max(c.b, 0.0f), 1.0f);
cclamped.a = std::min(std::max(c.a, 0.0f), 1.0f);
this->color = toColor32(cclamped);
color = toColor32(colorf);
}
void SpriteBatch::setColor()
Colorf SpriteBatch::getColor() const
{
color_active = false;
color = Color32(255, 255, 255, 255);
}
Colorf SpriteBatch::getColor(bool &active) const
{
active = color_active;
return toColorf(color);
return colorf;
}
int SpriteBatch::getCount() const
@@ -225,31 +236,24 @@ void SpriteBatch::setBufferSize(int newsize)
return;
size_t vertex_size = vertex_stride * 4 * newsize;
love::graphics::Buffer *new_array_buf = nullptr;
int new_next = std::min(next, newsize);
try
{
auto gfx = Module::getInstance<graphics::Graphics>(Module::M_GRAPHICS);
new_array_buf = gfx->newBuffer(vertex_size, nullptr, array_buf->getType(), array_buf->getUsage(), array_buf->getMapFlags());
void *new_vertex_data = realloc(vertex_data, vertex_size);
if (new_vertex_data == nullptr)
throw love::Exception("Out of memory.");
// Copy as much of the old data into the new GLBuffer as can fit.
size_t copy_size = vertex_stride * 4 * new_next;
array_buf->copyTo(0, copy_size, new_array_buf, 0);
}
catch (love::Exception &)
{
delete new_array_buf;
throw;
}
auto gfx = Module::getInstance<graphics::Graphics>(Module::M_GRAPHICS);
Buffer::Settings settings(array_buf->getUsageFlags(), array_buf->getDataUsage());
auto decl = Buffer::getCommonFormatDeclaration(vertex_format);
// We don't need to unmap the old GLBuffer since we're deleting it.
delete array_buf;
array_buf.set(gfx->newBuffer(settings, decl, nullptr, vertex_size, 0), Acquire::NORETAIN);
array_buf->fill(0, vertex_stride * 4 * new_next, new_vertex_data);
vertex_data = (uint8 *) new_vertex_data;
array_buf = new_array_buf;
size = newsize;
next = new_next;
}
@@ -258,23 +262,27 @@ int SpriteBatch::getBufferSize() const
return size;
}
void SpriteBatch::attachAttribute(const std::string &name, Mesh *mesh)
void SpriteBatch::attachAttribute(const std::string &name, Buffer *buffer, Mesh *mesh)
{
if ((buffer->getUsageFlags() & BUFFERUSAGEFLAG_VERTEX) == 0)
throw love::Exception("GraphicsBuffer must be created with vertex buffer support to be used as a SpriteBatch vertex attribute.");
AttachedAttribute oldattrib = {};
AttachedAttribute newattrib = {};
if (mesh->getVertexCount() < (size_t) next * 4)
throw love::Exception("Mesh has too few vertices to be attached to this SpriteBatch (at least %d vertices are required)", next*4);
if (buffer->getArrayLength() < (size_t) next * 4)
throw love::Exception("Buffer has too few vertices to be attached to this SpriteBatch (at least %d vertices are required)", next*4);
auto it = attached_attributes.find(name);
if (it != attached_attributes.end())
oldattrib = it->second;
newattrib.index = mesh->getAttributeIndex(name);
newattrib.index = buffer->getDataMemberIndex(name);
if (newattrib.index < 0)
throw love::Exception("The specified mesh does not have a vertex attribute named '%s'", name.c_str());
throw love::Exception("The specified Buffer does not have a vertex attribute named '%s'", name.c_str());
newattrib.buffer = buffer;
newattrib.mesh = mesh;
attached_attributes[name] = newattrib;
@@ -306,12 +314,10 @@ bool SpriteBatch::getDrawRange(int &start, int &count) const
void SpriteBatch::draw(Graphics *gfx, const Matrix4 &m)
{
using namespace vertex;
if (next == 0)
return;
gfx->flushStreamDraws();
gfx->flushBatchedDraws();
if (texture.get())
{
@@ -323,62 +329,57 @@ void SpriteBatch::draw(Graphics *gfx, const Matrix4 &m)
Shader::attachDefault(defaultshader);
}
if (Shader::current)
Shader::current->checkMainTexture(texture);
}
// Make sure the buffer isn't mapped when we draw (sends data to GPU if needed.)
array_buf->unmap();
if (Shader::current)
Shader::current->validateDrawState(PRIMITIVE_TRIANGLES, texture);
Attributes attributes;
flush(); // Upload any modified sprite data to the GPU.
VertexAttributes attributes;
BufferBindings buffers;
{
buffers.set(0, array_buf, 0);
attributes.setCommonFormat(vertex_format, 0);
if (!color_active)
attributes.disable(ATTRIB_COLOR);
}
int activebuffers = 1;
for (const auto &it : attached_attributes)
{
Mesh *mesh = it.second.mesh.get();
Buffer *buffer = it.second.buffer.get();
// We have to do this check here as wll because setBufferSize can be
// called after attachAttribute.
if (mesh->getVertexCount() < (size_t) next * 4)
throw love::Exception("Mesh with attribute '%s' attached to this SpriteBatch has too few vertices", it.first.c_str());
if (buffer->getArrayLength() < (size_t) next * 4)
throw love::Exception("Buffer with attribute '%s' attached to this SpriteBatch has too few vertices", it.first.c_str());
int attributeindex = -1;
// If the attribute is one of the LOVE-defined ones, use the constant
// attribute index for it, otherwise query the index from the shader.
BuiltinVertexAttribute builtinattrib;
if (vertex::getConstant(it.first.c_str(), builtinattrib))
if (getConstant(it.first.c_str(), builtinattrib))
attributeindex = (int) builtinattrib;
else if (Shader::current)
attributeindex = Shader::current->getVertexAttributeIndex(it.first);
if (attributeindex >= 0)
{
// Make sure the buffer isn't mapped (sends data to GPU if needed.)
mesh->vertexBuffer->unmap();
if (it.second.mesh.get())
it.second.mesh->flush();
const auto &formats = mesh->getVertexFormat();
const auto &format = formats[it.second.index];
const auto &member = buffer->getDataMember(it.second.index);
uint16 offset = (uint16) mesh->getAttributeOffset(it.second.index);
uint16 stride = (uint16) mesh->getVertexStride();
uint16 offset = (uint16) buffer->getMemberOffset(it.second.index);
uint16 stride = (uint16) buffer->getArrayStride();
attributes.set(attributeindex, format.type, (uint8) format.components, offset, activebuffers);
attributes.set(attributeindex, member.decl.format, offset, activebuffers);
attributes.setBufferLayout(activebuffers, stride);
// TODO: We should reuse buffer bindings with the same buffer+stride+step.
buffers.set(activebuffers, mesh->vertexBuffer, 0);
buffers.set(activebuffers, buffer, 0);
activebuffers++;
}
}
+19 -19
View File
@@ -30,6 +30,7 @@
#include "common/math.h"
#include "common/Matrix.h"
#include "common/Color.h"
#include "common/Range.h"
#include "Drawable.h"
#include "Mesh.h"
#include "vertex.h"
@@ -51,7 +52,7 @@ public:
static love::Type type;
SpriteBatch(Graphics *gfx, Texture *texture, int size, vertex::Usage usage);
SpriteBatch(Graphics *gfx, Texture *texture, int size, BufferDataUsage usage);
virtual ~SpriteBatch();
int add(const Matrix4 &m, int index = -1);
@@ -67,24 +68,17 @@ public:
Texture *getTexture() const;
/**
* Set the current color for this SpriteBatch. The sprites added
* after this call will use this color. Note that global color
* will not longer apply to the SpriteBatch if this is used.
* Set the current color for this SpriteBatch. The sprites added after this
* call will use this color.
*
* @param color The color to use for the following sprites.
*/
void setColor(const Colorf &color);
/**
* Disable per-sprite colors for this SpriteBatch. The next call to
* draw will use the global color for all sprites.
*/
void setColor();
/**
* Get the current color for this SpriteBatch.
**/
Colorf getColor(bool &active) const;
Colorf getColor() const;
/**
* Get the number of sprites currently in this SpriteBatch.
@@ -97,10 +91,13 @@ public:
int getBufferSize() const;
/**
* Attaches a specific vertex attribute from a Mesh to this SpriteBatch.
* Attaches a specific vertex attribute from a Buffer to this SpriteBatch.
* The vertex attribute will be used when drawing the SpriteBatch.
* If the attribute comes from a Mesh, it should be given as an argument as
* well, to make sure the SpriteBatch flushes its data to its Buffer when
* the SpriteBatch is drawn.
**/
void attachAttribute(const std::string &name, Mesh *mesh);
void attachAttribute(const std::string &name, Buffer *buffer, Mesh *mesh);
void setDrawRange(int start, int count);
void setDrawRange();
@@ -113,6 +110,7 @@ private:
struct AttachedAttribute
{
StrongRef<Buffer> buffer;
StrongRef<Mesh> mesh;
int index;
};
@@ -131,15 +129,17 @@ private:
// The next free element.
int next;
// Current color. This color, if present, will be applied to the next
// added sprite.
// Current color. This color will be applied to the next added sprite.
Color32 color;
bool color_active;
Colorf colorf;
vertex::CommonFormat vertex_format;
CommonFormat vertex_format;
size_t vertex_stride;
love::graphics::Buffer *array_buf;
StrongRef<love::graphics::Buffer> array_buf;
uint8 *vertex_data;
Range modified_sprites;
std::unordered_map<std::string, AttachedAttribute> attached_attributes;
+1 -1
View File
@@ -26,7 +26,7 @@ namespace love
namespace graphics
{
StreamBuffer::StreamBuffer(BufferType mode, size_t size)
StreamBuffer::StreamBuffer(BufferUsage mode, size_t size)
: bufferSize(size)
, frameGPUReadOffset(0)
, mode(mode)
+5 -4
View File
@@ -22,6 +22,7 @@
// LOVE
#include "common/int.h"
#include "common/Object.h"
#include "vertex.h"
#include "Resource.h"
@@ -33,7 +34,7 @@ namespace love
namespace graphics
{
class StreamBuffer : public Resource
class StreamBuffer : public love::Object, public Resource
{
public:
@@ -53,7 +54,7 @@ public:
virtual ~StreamBuffer() {}
size_t getSize() const { return bufferSize; }
BufferType getMode() const { return mode; }
BufferUsage getMode() const { return mode; }
size_t getUsableSize() const { return bufferSize - frameGPUReadOffset; }
virtual MapInfo map(size_t minsize) = 0;
@@ -64,11 +65,11 @@ public:
protected:
StreamBuffer(BufferType mode, size_t size);
StreamBuffer(BufferUsage mode, size_t size);
size_t bufferSize;
size_t frameGPUReadOffset;
BufferType mode;
BufferUsage mode;
}; // StreamBuffer
-270
View File
@@ -1,270 +0,0 @@
/**
* Copyright (c) 2006-2023 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 "Text.h"
#include "Graphics.h"
#include <algorithm>
namespace love
{
namespace graphics
{
love::Type Text::type("Text", &Drawable::type);
Text::Text(Font *font, const std::vector<Font::ColoredString> &text)
: font(font)
, vertexAttributes(Font::vertexFormat, 0)
, vertex_buffer(nullptr)
, vert_offset(0)
, texture_cache_id((uint32) -1)
{
set(text);
}
Text::~Text()
{
delete vertex_buffer;
}
void Text::uploadVertices(const std::vector<Font::GlyphVertex> &vertices, size_t vertoffset)
{
size_t offset = vertoffset * sizeof(Font::GlyphVertex);
size_t datasize = vertices.size() * sizeof(Font::GlyphVertex);
// If we haven't created a VBO or the vertices are too big, make a new one.
if (datasize > 0 && (!vertex_buffer || (offset + datasize) > vertex_buffer->getSize()))
{
// Make it bigger than necessary to reduce potential future allocations.
size_t newsize = size_t((offset + datasize) * 1.5);
if (vertex_buffer != nullptr)
newsize = std::max(size_t(vertex_buffer->getSize() * 1.5), newsize);
auto gfx = Module::getInstance<Graphics>(Module::M_GRAPHICS);
Buffer *new_buffer = gfx->newBuffer(newsize, nullptr, BUFFER_VERTEX, vertex::USAGE_DYNAMIC, 0);
if (vertex_buffer != nullptr)
vertex_buffer->copyTo(0, vertex_buffer->getSize(), new_buffer, 0);
delete vertex_buffer;
vertex_buffer = new_buffer;
vertexBuffers.set(0, vertex_buffer, 0);
}
if (vertex_buffer != nullptr && datasize > 0)
{
uint8 *bufferdata = (uint8 *) vertex_buffer->map();
memcpy(bufferdata + offset, &vertices[0], datasize);
// We unmap when we draw, to avoid unnecessary full map()/unmap() calls.
}
}
void Text::regenerateVertices()
{
// If the font's texture cache was invalidated then we need to recreate the
// text's vertices, since glyph texcoords might have changed.
if (font->getTextureCacheID() != texture_cache_id)
{
std::vector<TextData> textdata = text_data;
clear();
for (const TextData &t : textdata)
addTextData(t);
texture_cache_id = font->getTextureCacheID();
}
}
void Text::addTextData(const TextData &t)
{
std::vector<Font::GlyphVertex> vertices;
std::vector<Font::DrawCommand> new_commands;
Font::TextInfo text_info;
Colorf constantcolor = Colorf(1.0f, 1.0f, 1.0f, 1.0f);
// We only have formatted text if the align mode is valid.
if (t.align == Font::ALIGN_MAX_ENUM)
new_commands = font->generateVertices(t.codepoints, constantcolor, vertices, 0.0f, Vector2(0.0f, 0.0f), &text_info);
else
new_commands = font->generateVerticesFormatted(t.codepoints, constantcolor, t.wrap, t.align, vertices, &text_info);
size_t voffset = vert_offset;
if (!t.append_vertices)
{
voffset = 0;
vert_offset = 0;
draw_commands.clear();
text_data.clear();
}
if (t.use_matrix && !vertices.empty())
t.matrix.transformXY(vertices.data(), vertices.data(), (int) vertices.size());
uploadVertices(vertices, voffset);
if (!new_commands.empty())
{
// The start vertex should be adjusted to account for the vertex offset.
for (Font::DrawCommand &cmd : new_commands)
cmd.startvertex += (int) voffset;
auto firstcmd = new_commands.begin();
// If the first draw command in the new list has the same texture as the
// last one in the existing list we're building and its vertices are
// in-order, we can combine them (saving a draw call.)
if (!draw_commands.empty())
{
auto prevcmd = draw_commands.back();
if (prevcmd.texture == firstcmd->texture && (prevcmd.startvertex + prevcmd.vertexcount) == firstcmd->startvertex)
{
draw_commands.back().vertexcount += firstcmd->vertexcount;
++firstcmd;
}
}
// Append the new draw commands to the list we're building.
draw_commands.insert(draw_commands.end(), firstcmd, new_commands.end());
}
vert_offset = voffset + vertices.size();
text_data.push_back(t);
text_data.back().text_info = text_info;
// Font::generateVertices can invalidate the font's texture cache.
if (font->getTextureCacheID() != texture_cache_id)
regenerateVertices();
}
void Text::set(const std::vector<Font::ColoredString> &text)
{
return set(text, -1.0f, Font::ALIGN_MAX_ENUM);
}
void Text::set(const std::vector<Font::ColoredString> &text, float wrap, Font::AlignMode align)
{
if (text.empty() || (text.size() == 1 && text[0].str.empty()))
return clear();
Font::ColoredCodepoints codepoints;
Font::getCodepointsFromString(text, codepoints);
addTextData({codepoints, wrap, align, {}, false, false, Matrix4()});
}
int Text::add(const std::vector<Font::ColoredString> &text, const Matrix4 &m)
{
return addf(text, -1.0f, Font::ALIGN_MAX_ENUM, m);
}
int Text::addf(const std::vector<Font::ColoredString> &text, float wrap, Font::AlignMode align, const Matrix4 &m)
{
Font::ColoredCodepoints codepoints;
Font::getCodepointsFromString(text, codepoints);
addTextData({codepoints, wrap, align, {}, true, true, m});
return (int) text_data.size() - 1;
}
void Text::clear()
{
text_data.clear();
draw_commands.clear();
texture_cache_id = font->getTextureCacheID();
vert_offset = 0;
}
void Text::setFont(Font *f)
{
font.set(f);
// Invalidate the texture cache ID since the font is different. We also have
// to re-upload all the vertices based on the new font's textures.
texture_cache_id = (uint32) -1;
regenerateVertices();
}
Font *Text::getFont() const
{
return font.get();
}
int Text::getWidth(int index) const
{
if (index < 0)
index = std::max((int) text_data.size() - 1, 0);
if (index >= (int) text_data.size())
return 0;
return text_data[index].text_info.width;
}
int Text::getHeight(int index) const
{
if (index < 0)
index = std::max((int) text_data.size() - 1, 0);
if (index >= (int) text_data.size())
return 0;
return text_data[index].text_info.height;
}
void Text::draw(Graphics *gfx, const Matrix4 &m)
{
if (vertex_buffer == nullptr || draw_commands.empty())
return;
gfx->flushStreamDraws();
if (Shader::isDefaultActive())
Shader::attachDefault(Shader::STANDARD_DEFAULT);
if (Shader::current)
Shader::current->checkMainTextureType(TEXTURE_2D, false);
// Re-generate the text if the Font's texture cache was invalidated.
if (font->getTextureCacheID() != texture_cache_id)
regenerateVertices();
int totalverts = 0;
for (const Font::DrawCommand &cmd : draw_commands)
totalverts = std::max(cmd.startvertex + cmd.vertexcount, totalverts);
vertex_buffer->unmap(); // Make sure all pending data is flushed to the GPU.
Graphics::TempTransform transform(gfx, m);
for (const Font::DrawCommand &cmd : draw_commands)
gfx->drawQuads(cmd.startvertex / 4, cmd.vertexcount / 4, vertexAttributes, vertexBuffers, cmd.texture);
}
} // graphics
} // love
+297
View File
@@ -0,0 +1,297 @@
/**
* Copyright (c) 2006-2023 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 "TextBatch.h"
#include "Graphics.h"
#include <algorithm>
namespace love
{
namespace graphics
{
love::Type TextBatch::type("TextBatch", &Drawable::type);
TextBatch::TextBatch(Font *font, const std::vector<love::font::ColoredString> &text)
: font(font)
, vertexAttributes(Font::vertexFormat, 0)
, vertexData(nullptr)
, modifiedVertices()
, vertOffset(0)
, textureCacheID((uint32) -1)
{
set(text);
}
TextBatch::~TextBatch()
{
if (vertexData != nullptr)
free(vertexData);
}
void TextBatch::uploadVertices(const std::vector<Font::GlyphVertex> &vertices, size_t vertoffset)
{
size_t offset = vertoffset * sizeof(Font::GlyphVertex);
size_t datasize = vertices.size() * sizeof(Font::GlyphVertex);
// If we haven't created a VBO or the vertices are too big, make a new one.
if (datasize > 0 && (!vertexBuffer || (offset + datasize) > vertexBuffer->getSize()))
{
// Make it bigger than necessary to reduce potential future allocations.
size_t newsize = size_t((offset + datasize) * 1.5);
if (vertexBuffer != nullptr)
newsize = std::max(size_t(vertexBuffer->getSize() * 1.5), newsize);
auto gfx = Module::getInstance<Graphics>(Module::M_GRAPHICS);
Buffer::Settings settings(BUFFERUSAGEFLAG_VERTEX, BUFFERDATAUSAGE_DYNAMIC);
auto decl = Buffer::getCommonFormatDeclaration(Font::vertexFormat);
Buffer *newbuffer = gfx->newBuffer(settings, decl, nullptr, newsize, 0);
void *newdata = nullptr;
if (vertexData != nullptr)
newdata = realloc(vertexData, newsize);
else
newdata = malloc(newsize);
if (newdata == nullptr)
throw love::Exception("Out of memory.");
else
vertexData = (uint8 *) newdata;
vertexBuffer = newbuffer;
vertexBuffers.set(0, vertexBuffer, 0);
}
if (vertexData != nullptr && datasize > 0)
{
memcpy(vertexData + offset, &vertices[0], datasize);
modifiedVertices.encapsulate(offset, datasize);
}
}
void TextBatch::regenerateVertices()
{
// If the font's texture cache was invalidated then we need to recreate the
// text's vertices, since glyph texcoords might have changed.
if (font->getTextureCacheID() != textureCacheID)
{
std::vector<TextData> textdata = textData;
clear();
for (const TextData &t : textdata)
addTextData(t);
textureCacheID = font->getTextureCacheID();
}
}
void TextBatch::addTextData(const TextData &t)
{
std::vector<Font::GlyphVertex> vertices;
std::vector<Font::DrawCommand> newcommands;
love::font::TextShaper::TextInfo textinfo;
Colorf constantcolor = Colorf(1.0f, 1.0f, 1.0f, 1.0f);
// We only have formatted text if the align mode is valid.
if (t.align == Font::ALIGN_MAX_ENUM)
newcommands = font->generateVertices(t.codepoints, Range(), constantcolor, vertices, 0.0f, Vector2(0.0f, 0.0f), &textinfo);
else
newcommands = font->generateVerticesFormatted(t.codepoints, constantcolor, t.wrap, t.align, vertices, &textinfo);
size_t voffset = vertOffset;
if (!t.appendVertices)
{
voffset = 0;
vertOffset = 0;
drawCommands.clear();
textData.clear();
}
if (t.useMatrix && !vertices.empty())
t.matrix.transformXY(vertices.data(), vertices.data(), (int) vertices.size());
uploadVertices(vertices, voffset);
if (!newcommands.empty())
{
// The start vertex should be adjusted to account for the vertex offset.
for (Font::DrawCommand &cmd : newcommands)
cmd.startvertex += (int) voffset;
auto firstcmd = newcommands.begin();
// If the first draw command in the new list has the same texture as the
// last one in the existing list we're building and its vertices are
// in-order, we can combine them (saving a draw call.)
if (!drawCommands.empty())
{
auto prevcmd = drawCommands.back();
if (prevcmd.texture == firstcmd->texture && (prevcmd.startvertex + prevcmd.vertexcount) == firstcmd->startvertex)
{
drawCommands.back().vertexcount += firstcmd->vertexcount;
++firstcmd;
}
}
// Append the new draw commands to the list we're building.
drawCommands.insert(drawCommands.end(), firstcmd, newcommands.end());
}
vertOffset = voffset + vertices.size();
textData.push_back(t);
textData.back().textInfo = textinfo;
// Font::generateVertices can invalidate the font's texture cache.
if (font->getTextureCacheID() != textureCacheID)
regenerateVertices();
}
void TextBatch::set(const std::vector<love::font::ColoredString> &text)
{
return set(text, -1.0f, Font::ALIGN_MAX_ENUM);
}
void TextBatch::set(const std::vector<love::font::ColoredString> &text, float wrap, Font::AlignMode align)
{
if (text.empty() || (text.size() == 1 && text[0].str.empty()))
return clear();
love::font::ColoredCodepoints codepoints;
love::font::getCodepointsFromString(text, codepoints);
addTextData({codepoints, wrap, align, {}, false, false, Matrix4()});
}
int TextBatch::add(const std::vector<love::font::ColoredString> &text, const Matrix4 &m)
{
return addf(text, -1.0f, Font::ALIGN_MAX_ENUM, m);
}
int TextBatch::addf(const std::vector<love::font::ColoredString> &text, float wrap, Font::AlignMode align, const Matrix4 &m)
{
love::font::ColoredCodepoints codepoints;
love::font::getCodepointsFromString(text, codepoints);
addTextData({codepoints, wrap, align, {}, true, true, m});
return (int) textData.size() - 1;
}
void TextBatch::clear()
{
textData.clear();
drawCommands.clear();
textureCacheID = font->getTextureCacheID();
vertOffset = 0;
}
void TextBatch::setFont(Font *f)
{
font.set(f);
// Invalidate the texture cache ID since the font is different. We also have
// to re-upload all the vertices based on the new font's textures.
textureCacheID = (uint32) -1;
regenerateVertices();
}
Font *TextBatch::getFont() const
{
return font.get();
}
int TextBatch::getWidth(int index) const
{
if (index < 0)
index = std::max((int) textData.size() - 1, 0);
if (index >= (int) textData.size())
return 0;
return textData[index].textInfo.width;
}
int TextBatch::getHeight(int index) const
{
if (index < 0)
index = std::max((int) textData.size() - 1, 0);
if (index >= (int) textData.size())
return 0;
return textData[index].textInfo.height;
}
void TextBatch::draw(Graphics *gfx, const Matrix4 &m)
{
if (vertexBuffer == nullptr || vertexData == nullptr || drawCommands.empty())
return;
gfx->flushBatchedDraws();
// Re-generate the text if the Font's texture cache was invalidated.
if (font->getTextureCacheID() != textureCacheID)
regenerateVertices();
if (Shader::isDefaultActive())
Shader::attachDefault(Shader::STANDARD_DEFAULT);
Texture *firsttex = nullptr;
if (!drawCommands.empty())
firsttex = drawCommands[0].texture;
if (Shader::current)
Shader::current->validateDrawState(PRIMITIVE_TRIANGLES, firsttex);
int totalverts = 0;
for (const Font::DrawCommand &cmd : drawCommands)
totalverts = std::max(cmd.startvertex + cmd.vertexcount, totalverts);
// Make sure all pending data is uploaded to the GPU.
if (modifiedVertices.isValid())
{
size_t offset = modifiedVertices.getOffset();
size_t size = modifiedVertices.getSize();
if (vertexBuffer->getDataUsage() == BUFFERDATAUSAGE_STREAM)
vertexBuffer->fill(0, vertexBuffer->getSize(), vertexData);
else
vertexBuffer->fill(offset, size, vertexData + offset);
modifiedVertices.invalidate();
}
Graphics::TempTransform transform(gfx, m);
for (const Font::DrawCommand &cmd : drawCommands)
gfx->drawQuads(cmd.startvertex / 4, cmd.vertexcount / 4, vertexAttributes, vertexBuffers, cmd.texture);
}
} // graphics
} // love
@@ -22,6 +22,7 @@
// LOVE
#include "common/config.h"
#include "common/Range.h"
#include "Drawable.h"
#include "Font.h"
#include "Buffer.h"
@@ -33,20 +34,20 @@ namespace graphics
class Graphics;
class Text : public Drawable
class TextBatch : public Drawable
{
public:
static love::Type type;
Text(Font *font, const std::vector<Font::ColoredString> &text = {});
virtual ~Text();
TextBatch(Font *font, const std::vector<love::font::ColoredString> &text = {});
virtual ~TextBatch();
void set(const std::vector<Font::ColoredString> &text);
void set(const std::vector<Font::ColoredString> &text, float wrap, Font::AlignMode align);
void set(const std::vector<love::font::ColoredString> &text);
void set(const std::vector<love::font::ColoredString> &text, float wrap, Font::AlignMode align);
int add(const std::vector<Font::ColoredString> &text, const Matrix4 &m);
int addf(const std::vector<Font::ColoredString> &text, float wrap, Font::AlignMode align, const Matrix4 &m);
int add(const std::vector<love::font::ColoredString> &text, const Matrix4 &m);
int addf(const std::vector<love::font::ColoredString> &text, float wrap, Font::AlignMode align, const Matrix4 &m);
void clear();
@@ -70,12 +71,12 @@ private:
struct TextData
{
Font::ColoredCodepoints codepoints;
love::font::ColoredCodepoints codepoints;
float wrap;
Font::AlignMode align;
Font::TextInfo text_info;
bool use_matrix;
bool append_vertices;
love::font::TextShaper::TextInfo textInfo;
bool useMatrix;
bool appendVertices;
Matrix4 matrix;
};
@@ -85,19 +86,21 @@ private:
StrongRef<Font> font;
vertex::Attributes vertexAttributes;
vertex::BufferBindings vertexBuffers;
VertexAttributes vertexAttributes;
BufferBindings vertexBuffers;
Buffer *vertex_buffer;
StrongRef<Buffer> vertexBuffer;
uint8 *vertexData;
Range modifiedVertices;
std::vector<Font::DrawCommand> draw_commands;
std::vector<Font::DrawCommand> drawCommands;
std::vector<TextData> text_data;
std::vector<TextData> textData;
size_t vert_offset;
size_t vertOffset;
// Used so we know when the font's texture cache is invalidated.
uint32 texture_cache_id;
uint32 textureCacheID;
}; // Text
File diff suppressed because it is too large Load Diff
+201 -65
View File
@@ -31,8 +31,11 @@
#include "Drawable.h"
#include "Quad.h"
#include "vertex.h"
#include "depthstencil.h"
#include "renderstate.h"
#include "Resource.h"
#include "image/ImageData.h"
#include "image/Image.h"
#include "image/CompressedImageData.h"
// C
#include <stddef.h>
@@ -43,6 +46,7 @@ namespace graphics
{
class Graphics;
class Buffer;
enum TextureType
{
@@ -53,6 +57,90 @@ enum TextureType
TEXTURE_MAX_ENUM
};
enum PixelFormatUsage
{
PIXELFORMATUSAGE_SAMPLE, // Any sampling in shaders.
PIXELFORMATUSAGE_LINEAR, // Linear filtering.
PIXELFORMATUSAGE_RENDERTARGET, // Usable as a render target.
PIXELFORMATUSAGE_BLEND, // Blend support when used as a render target.
PIXELFORMATUSAGE_MSAA, // MSAA support when used as a render target.
PIXELFORMATUSAGE_COMPUTEWRITE, // Writable in compute shaders via imageStore.
PIXELFORMATUSAGE_MAX_ENUM
};
enum PixelFormatUsageFlags
{
PIXELFORMATUSAGEFLAGS_NONE = 0,
PIXELFORMATUSAGEFLAGS_SAMPLE = (1 << PIXELFORMATUSAGE_SAMPLE),
PIXELFORMATUSAGEFLAGS_LINEAR = (1 << PIXELFORMATUSAGE_LINEAR),
PIXELFORMATUSAGEFLAGS_RENDERTARGET = (1 << PIXELFORMATUSAGE_RENDERTARGET),
PIXELFORMATUSAGEFLAGS_BLEND = (1 << PIXELFORMATUSAGE_BLEND),
PIXELFORMATUSAGEFLAGS_MSAA = (1 << PIXELFORMATUSAGE_MSAA),
PIXELFORMATUSAGEFLAGS_COMPUTEWRITE = (1 << PIXELFORMATUSAGE_COMPUTEWRITE),
};
struct SamplerState
{
enum WrapMode
{
WRAP_CLAMP,
WRAP_CLAMP_ZERO,
WRAP_CLAMP_ONE,
WRAP_REPEAT,
WRAP_MIRRORED_REPEAT,
WRAP_MAX_ENUM
};
enum FilterMode
{
FILTER_LINEAR,
FILTER_NEAREST,
FILTER_MAX_ENUM
};
enum MipmapFilterMode
{
MIPMAP_FILTER_NONE,
MIPMAP_FILTER_LINEAR,
MIPMAP_FILTER_NEAREST,
MIPMAP_FILTER_MAX_ENUM
};
FilterMode minFilter = FILTER_LINEAR;
FilterMode magFilter = FILTER_LINEAR;
MipmapFilterMode mipmapFilter = MIPMAP_FILTER_NONE;
WrapMode wrapU = WRAP_CLAMP;
WrapMode wrapV = WRAP_CLAMP;
WrapMode wrapW = WRAP_CLAMP;
float lodBias = 0.0f;
uint8 maxAnisotropy = 1;
uint8 minLod = 0;
uint8 maxLod = LOVE_UINT8_MAX;
Optional<CompareMode> depthSampleMode;
uint64 toKey() const;
static SamplerState fromKey(uint64 key);
static bool isClampZeroOrOne(WrapMode w);
static bool getConstant(const char *in, FilterMode &out);
static bool getConstant(FilterMode in, const char *&out);
static std::vector<std::string> getConstants(FilterMode);
static bool getConstant(const char *in, MipmapFilterMode &out);
static bool getConstant(MipmapFilterMode in, const char *&out);
static std::vector<std::string> getConstants(MipmapFilterMode);
static bool getConstant(const char *in, WrapMode &out);
static bool getConstant(WrapMode in, const char *&out);
static std::vector<std::string> getConstants(WrapMode);
};
/**
* Base class for 2D textures. All textures can be drawn with Quads, have a
* width and height, and have filter and wrap modes.
@@ -62,46 +150,85 @@ class Texture : public Drawable, public Resource
public:
static love::Type type;
static int textureCount;
enum WrapMode
enum MipmapsMode
{
WRAP_CLAMP,
WRAP_CLAMP_ZERO,
WRAP_REPEAT,
WRAP_MIRRORED_REPEAT,
WRAP_MAX_ENUM
MIPMAPS_NONE,
MIPMAPS_MANUAL,
MIPMAPS_AUTO,
MIPMAPS_MAX_ENUM
};
enum FilterMode
enum SettingType
{
FILTER_NONE,
FILTER_LINEAR,
FILTER_NEAREST,
FILTER_MAX_ENUM
SETTING_WIDTH,
SETTING_HEIGHT,
SETTING_LAYERS,
SETTING_MIPMAPS,
SETTING_MIPMAP_COUNT,
SETTING_FORMAT,
SETTING_LINEAR,
SETTING_TYPE,
SETTING_DPI_SCALE,
SETTING_MSAA,
SETTING_RENDER_TARGET,
SETTING_COMPUTE_WRITE,
SETTING_READABLE,
SETTING_MAX_ENUM
};
struct Filter
// Size and format will be overridden by ImageData when supplied.
struct Settings
{
FilterMode min = FILTER_LINEAR;
FilterMode mag = FILTER_LINEAR;
FilterMode mipmap = FILTER_NONE;
float anisotropy = 1.0f;
int width = 1;
int height = 1;
int layers = 1; // depth for 3D textures
TextureType type = TEXTURE_2D;
MipmapsMode mipmaps = MIPMAPS_NONE;
int mipmapCount = 0; // only used when > 0.
PixelFormat format = PIXELFORMAT_NORMAL;
bool linear = false;
float dpiScale = 1.0f;
int msaa = 1;
bool renderTarget = false;
bool computeWrite = false;
OptionalBool readable;
};
struct Wrap
struct Slices
{
WrapMode s = WRAP_CLAMP;
WrapMode t = WRAP_CLAMP;
WrapMode r = WRAP_CLAMP;
};
public:
static Filter defaultFilter;
static FilterMode defaultMipmapFilter;
static float defaultMipmapSharpness;
Slices(TextureType textype);
void clear();
void set(int slice, int mipmap, love::image::ImageDataBase *data);
love::image::ImageDataBase *get(int slice, int mipmap) const;
void add(love::image::CompressedImageData *cdata, int startslice, int startmip, bool addallslices, bool addallmips);
int getSliceCount(int mip = 0) const;
int getMipmapCount(int slice = 0) const;
bool validate() const;
TextureType getTextureType() const { return textureType; }
private:
TextureType textureType;
// For 2D/Cube/2DArray texture types, each element in the data array has
// an array of mipmap levels. For 3D texture types, each mipmap level
// has an array of layers.
std::vector<std::vector<StrongRef<love::image::ImageDataBase>>> data;
}; // Slices
static int64 totalGraphicsMemory;
Texture(TextureType texType);
Texture(Graphics *gfx, const Settings &settings, const Slices *slices);
virtual ~Texture();
// Drawable.
@@ -110,17 +237,37 @@ public:
/**
* Draws the texture using the specified transformation with a Quad applied.
**/
virtual void draw(Graphics *gfx, Quad *quad, const Matrix4 &m);
void draw(Graphics *gfx, Quad *quad, const Matrix4 &m);
void drawLayer(Graphics *gfx, int layer, const Matrix4 &m);
virtual void drawLayer(Graphics *gfx, int layer, Quad *quad, const Matrix4 &m);
void drawLayer(Graphics *gfx, int layer, Quad *quad, const Matrix4 &m);
void replacePixels(love::image::ImageDataBase *d, int slice, int mipmap, int x, int y, bool reloadmipmaps);
void replacePixels(const void *data, size_t size, int slice, int mipmap, const Rect &rect, bool reloadmipmaps);
void generateMipmaps();
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;
virtual ptrdiff_t getRenderTargetHandle() const = 0;
virtual ptrdiff_t getSamplerHandle() const = 0;
TextureType getTextureType() const;
PixelFormat getPixelFormat() const;
MipmapsMode getMipmapsMode() const;
bool isRenderTarget() const;
bool isComputeWritable() const;
bool isReadable() const;
bool isValidSlice(int slice) const;
bool isCompressed() const;
bool isFormatLinear() const;
bool isValidSlice(int slice, int mip) const;
// Number of array layers, cube faces, or volume layers for the given mip.
int getSliceCount(int mip) const;
int getWidth(int mip = 0) const;
int getHeight(int mip = 0) const;
@@ -133,23 +280,14 @@ public:
float getDPIScale() const;
virtual void setFilter(const Filter &f);
virtual const Filter &getFilter() const;
int getRequestedMSAA() const;
virtual int getMSAA() const = 0;
virtual bool setWrap(const Wrap &w) = 0;
virtual const Wrap &getWrap() const;
// Sets the mipmap texture LOD bias (sharpness) value.
virtual bool setMipmapSharpness(float sharpness) = 0;
float getMipmapSharpness() const;
virtual void setDepthSampleMode(Optional<CompareMode> mode = Optional<CompareMode>());
Optional<CompareMode> getDepthSampleMode() const;
virtual void setSamplerState(const SamplerState &s);
const SamplerState &getSamplerState() const;
Quad *getQuad() const;
static bool validateFilter(const Filter &f, bool mipmapsAllowed);
static int getTotalMipmapCount(int w, int h);
static int getTotalMipmapCount(int w, int h, int d);
@@ -157,26 +295,38 @@ public:
static bool getConstant(TextureType in, const char *&out);
static std::vector<std::string> getConstants(TextureType);
static bool getConstant(const char *in, FilterMode &out);
static bool getConstant(FilterMode in, const char *&out);
static std::vector<std::string> getConstants(FilterMode);
static bool getConstant(const char *in, MipmapsMode &out);
static bool getConstant(MipmapsMode in, const char *&out);
static std::vector<std::string> getConstants(MipmapsMode);
static bool getConstant(const char *in, WrapMode &out);
static bool getConstant(WrapMode in, const char *&out);
static std::vector<std::string> getConstants(WrapMode);
static bool getConstant(const char *in, SettingType &out);
static bool getConstant(SettingType in, const char *&out);
static const char *getConstant(SettingType in);
static std::vector<std::string> getConstants(SettingType);
protected:
void initQuad();
void setGraphicsMemorySize(int64 size);
void uploadImageData(love::image::ImageDataBase *d, int level, int slice, int x, int y);
virtual void uploadByteData(PixelFormat pixelformat, const void *data, size_t size, int level, int slice, const Rect &r) = 0;
bool supportsGenerateMipmaps(const char *&outReason) const;
virtual void generateMipmapsInternal() = 0;
bool validateDimensions(bool throwException) const;
TextureType texType;
PixelFormat format;
bool renderTarget;
bool computeWrite;
bool readable;
MipmapsMode mipmapsMode;
bool sRGB;
int width;
int height;
@@ -187,28 +337,14 @@ protected:
int pixelWidth;
int pixelHeight;
Filter filter;
Wrap wrap;
int requestedMSAA;
float mipmapSharpness;
Optional<CompareMode> depthCompareMode;
SamplerState samplerState;
StrongRef<Quad> quad;
int64 graphicsMemorySize;
private:
static StringMap<TextureType, TEXTURE_MAX_ENUM>::Entry texTypeEntries[];
static StringMap<TextureType, TEXTURE_MAX_ENUM> texTypes;
static StringMap<FilterMode, FILTER_MAX_ENUM>::Entry filterModeEntries[];
static StringMap<FilterMode, FILTER_MAX_ENUM> filterModes;
static StringMap<WrapMode, WRAP_MAX_ENUM>::Entry wrapModeEntries[];
static StringMap<WrapMode, WRAP_MAX_ENUM> wrapModes;
}; // Texture
} // graphics
+36 -26
View File
@@ -35,9 +35,14 @@ Video::Video(Graphics *gfx, love::video::VideoStream *stream, float dpiscale)
: stream(stream)
, width(stream->getWidth() / dpiscale)
, height(stream->getHeight() / dpiscale)
, filter(Texture::defaultFilter)
, samplerState()
{
filter.mipmap = Texture::FILTER_NONE;
const SamplerState &defaultSampler = gfx->getDefaultSamplerState();
samplerState.minFilter = defaultSampler.minFilter;
samplerState.magFilter = defaultSampler.magFilter;
samplerState.wrapU = defaultSampler.wrapU;
samplerState.wrapV = defaultSampler.wrapV;
samplerState.maxAnisotropy = defaultSampler.maxAnisotropy;
stream->fillBackBuffer();
@@ -74,23 +79,24 @@ Video::Video(Graphics *gfx, love::video::VideoStream *stream, float dpiscale)
const unsigned char *data[3] = {frame->yplane, frame->cbplane, frame->crplane};
Texture::Wrap wrap; // Clamp wrap mode.
Image::Settings settings;
Texture::Settings settings;
for (int i = 0; i < 3; i++)
{
Image *img = gfx->newImage(TEXTURE_2D, PIXELFORMAT_R8, widths[i], heights[i], 1, settings);
settings.width = widths[i];
settings.height = heights[i];
settings.format = PIXELFORMAT_R8_UNORM;
Texture *tex = gfx->newTexture(settings, nullptr);
img->setFilter(filter);
img->setWrap(wrap);
tex->setSamplerState(samplerState);
size_t bpp = getPixelFormatSize(PIXELFORMAT_R8);
size_t bpp = getPixelFormatBlockSize(PIXELFORMAT_R8_UNORM);
size_t size = bpp * widths[i] * heights[i];
Rect rect = {0, 0, widths[i], heights[i]};
img->replacePixels(data[i], size, 0, 0, rect, false);
tex->replacePixels(data[i], size, 0, 0, rect, false);
images[i].set(img, Acquire::NORETAIN);
textures[i].set(tex, Acquire::NORETAIN);
}
}
@@ -114,21 +120,21 @@ void Video::draw(Graphics *gfx, const Matrix4 &m)
Matrix4 t(tm, m);
Graphics::StreamDrawCommand cmd;
cmd.formats[0] = vertex::getSinglePositionFormat(is2D);
cmd.formats[1] = vertex::CommonFormat::STf_RGBAub;
cmd.indexMode = vertex::TriangleIndexMode::QUADS;
Graphics::BatchedDrawCommand cmd;
cmd.formats[0] = getSinglePositionFormat(is2D);
cmd.formats[1] = CommonFormat::STf_RGBAub;
cmd.indexMode = TRIANGLEINDEX_QUADS;
cmd.vertexCount = 4;
cmd.standardShaderType = Shader::STANDARD_VIDEO;
Graphics::StreamVertexData data = gfx->requestStreamDraw(cmd);
Graphics::BatchedVertexData data = gfx->requestBatchedDraw(cmd);
if (is2D)
t.transformXY((Vector2 *) data.stream[0], vertices, 4);
else
t.transformXY0((Vector3 *) data.stream[0], vertices, 4);
vertex::STf_RGBAub *verts = (vertex::STf_RGBAub *) data.stream[1];
STf_RGBAub *verts = (STf_RGBAub *) data.stream[1];
Color32 c = toColor32(gfx->getColor());
@@ -140,9 +146,9 @@ void Video::draw(Graphics *gfx, const Matrix4 &m)
}
if (Shader::current != nullptr)
Shader::current->setVideoTextures(images[0], images[1], images[2]);
Shader::current->setVideoTextures(textures[0], textures[1], textures[2]);
gfx->flushStreamDraws();
gfx->flushBatchedDraws();
}
void Video::update()
@@ -161,11 +167,11 @@ void Video::update()
for (int i = 0; i < 3; i++)
{
size_t bpp = getPixelFormatSize(PIXELFORMAT_R8);
size_t bpp = getPixelFormatBlockSize(PIXELFORMAT_R8_UNORM);
size_t size = bpp * widths[i] * heights[i];
Rect rect = {0, 0, widths[i], heights[i]};
images[i]->replacePixels(data[i], size, 0, 0, rect, false);
textures[i]->replacePixels(data[i], size, 0, 0, rect, false);
}
}
}
@@ -200,17 +206,21 @@ int Video::getPixelHeight() const
return stream->getHeight();
}
void Video::setFilter(const Texture::Filter &f)
void Video::setSamplerState(const SamplerState &s)
{
for (const auto &image : images)
image->setFilter(f);
samplerState.minFilter = s.minFilter;
samplerState.magFilter = s.magFilter;
samplerState.wrapU = s.wrapU;
samplerState.wrapV = s.wrapV;
samplerState.maxAnisotropy = s.maxAnisotropy;
filter = f;
for (const auto &texture : textures)
texture->setSamplerState(samplerState);
}
const Texture::Filter &Video::getFilter() const
const SamplerState &Video::getSamplerState() const
{
return filter;
return samplerState;
}
} // graphics
+5 -5
View File
@@ -23,7 +23,7 @@
// LOVE
#include "common/math.h"
#include "Drawable.h"
#include "Image.h"
#include "Texture.h"
#include "vertex.h"
#include "video/VideoStream.h"
#include "audio/Source.h"
@@ -58,8 +58,8 @@ public:
int getPixelWidth() const;
int getPixelHeight() const;
void setFilter(const Texture::Filter &f);
const Texture::Filter &getFilter() const;
void setSamplerState(const SamplerState &s);
const SamplerState &getSamplerState() const;
private:
@@ -70,11 +70,11 @@ private:
int width;
int height;
Texture::Filter filter;
SamplerState samplerState;
Vertex vertices[4];
StrongRef<Image> images[3];
StrongRef<Texture> textures[3];
StrongRef<love::audio::Source> source;
}; // Video
-103
View File
@@ -1,103 +0,0 @@
/**
* Copyright (c) 2006-2023 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 "depthstencil.h"
#include "common/StringMap.h"
namespace love
{
namespace graphics
{
CompareMode getReversedCompareMode(CompareMode mode)
{
switch (mode)
{
case COMPARE_LESS:
return COMPARE_GREATER;
case COMPARE_LEQUAL:
return COMPARE_GEQUAL;
case COMPARE_GEQUAL:
return COMPARE_LEQUAL;
case COMPARE_GREATER:
return COMPARE_LESS;
default:
return mode;
}
}
static StringMap<StencilAction, STENCIL_MAX_ENUM>::Entry stencilActionEntries[] =
{
{ "replace", STENCIL_REPLACE },
{ "increment", STENCIL_INCREMENT },
{ "decrement", STENCIL_DECREMENT },
{ "incrementwrap", STENCIL_INCREMENT_WRAP },
{ "decrementwrap", STENCIL_DECREMENT_WRAP },
{ "invert", STENCIL_INVERT },
};
static StringMap<StencilAction, STENCIL_MAX_ENUM> stencilActions(stencilActionEntries, sizeof(stencilActionEntries));
static StringMap<CompareMode, COMPARE_MAX_ENUM>::Entry compareModeEntries[] =
{
{ "less", COMPARE_LESS },
{ "lequal", COMPARE_LEQUAL },
{ "equal", COMPARE_EQUAL },
{ "gequal", COMPARE_GEQUAL },
{ "greater", COMPARE_GREATER },
{ "notequal", COMPARE_NOTEQUAL },
{ "always", COMPARE_ALWAYS },
{ "never", COMPARE_NEVER },
};
static StringMap<CompareMode, COMPARE_MAX_ENUM> compareModes(compareModeEntries, sizeof(compareModeEntries));
bool getConstant(const char *in, StencilAction &out)
{
return stencilActions.find(in, out);
}
bool getConstant(StencilAction in, const char *&out)
{
return stencilActions.find(in, out);
}
std::vector<std::string> getConstants(StencilAction)
{
return stencilActions.getNames();
}
bool getConstant(const char *in, CompareMode &out)
{
return compareModes.find(in, out);
}
bool getConstant(CompareMode in, const char *&out)
{
return compareModes.find(in, out);
}
std::vector<std::string> getConstants(CompareMode)
{
return compareModes.getNames();
}
} // graphics
} // love
-74
View File
@@ -1,74 +0,0 @@
/**
* Copyright (c) 2006-2023 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
#include <vector>
#include <string>
namespace love
{
namespace graphics
{
enum StencilAction
{
STENCIL_REPLACE,
STENCIL_INCREMENT,
STENCIL_DECREMENT,
STENCIL_INCREMENT_WRAP,
STENCIL_DECREMENT_WRAP,
STENCIL_INVERT,
STENCIL_MAX_ENUM
};
enum CompareMode
{
COMPARE_LESS,
COMPARE_LEQUAL,
COMPARE_EQUAL,
COMPARE_GEQUAL,
COMPARE_GREATER,
COMPARE_NOTEQUAL,
COMPARE_ALWAYS,
COMPARE_NEVER,
COMPARE_MAX_ENUM
};
/**
* GPU APIs do the comparison in the opposite way of what makes sense for some
* of love's APIs. For example in OpenGL if the compare function is GL_GREATER,
* then the stencil test will pass if the reference value is greater than the
* value in the stencil buffer. With our stencil API it's more intuitive to
* assume that setStencilTest(COMPARE_GREATER, 4) will make it pass if the
* stencil buffer has a value greater than 4.
**/
CompareMode getReversedCompareMode(CompareMode mode);
bool getConstant(const char *in, StencilAction &out);
bool getConstant(StencilAction in, const char *&out);
std::vector<std::string> getConstants(StencilAction);
bool getConstant(const char *in, CompareMode &out);
bool getConstant(CompareMode in, const char *&out);
std::vector<std::string> getConstants(CompareMode);
} // graphics
} // love
+63
View File
@@ -0,0 +1,63 @@
/**
* Copyright (c) 2006-2023 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
#include "graphics/Buffer.h"
#include "Metal.h"
#include "common/Range.h"
namespace love
{
namespace graphics
{
namespace metal
{
class Buffer final : public love::graphics::Buffer
{
public:
Buffer(love::graphics::Graphics *gfx, id<MTLDevice> device, const Settings &settings, const std::vector<DataDeclaration> &format, const void *data, size_t size, size_t arraylength);
virtual ~Buffer();
void *map(MapType map, size_t offset, size_t size) override;
void unmap(size_t usedoffset, size_t usedsize) override;
bool fill(size_t offset, size_t size, const void *data) override;
void clear(size_t offset, size_t size) 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; }
ptrdiff_t getTexelBufferHandle() const override { return (ptrdiff_t) texture; }
private:
id<MTLBuffer> buffer;
id<MTLTexture> texture;
id<MTLBuffer> mapBuffer;
Range mappedRange;
}; // Buffer
} // metal
} // graphics
} // love
+240
View File
@@ -0,0 +1,240 @@
/**
* Copyright (c) 2006-2023 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 "Buffer.h"
#include "Graphics.h"
namespace love
{
namespace graphics
{
namespace metal
{
static MTLPixelFormat getMTLPixelFormat(DataFormat format)
{
switch (format)
{
case DATAFORMAT_FLOAT: return MTLPixelFormatR32Float;
case DATAFORMAT_FLOAT_VEC2: return MTLPixelFormatRG32Float;
case DATAFORMAT_FLOAT_VEC4: return MTLPixelFormatRGBA32Float;
case DATAFORMAT_INT32: return MTLPixelFormatR32Sint;
case DATAFORMAT_INT32_VEC2: return MTLPixelFormatRG32Sint;
case DATAFORMAT_INT32_VEC4: return MTLPixelFormatRGBA32Sint;
case DATAFORMAT_UINT32: return MTLPixelFormatR32Uint;
case DATAFORMAT_UINT32_VEC2: return MTLPixelFormatRG32Uint;
case DATAFORMAT_UINT32_VEC4: return MTLPixelFormatRGBA32Uint;
case DATAFORMAT_UNORM8_VEC4: return MTLPixelFormatRGBA8Unorm;
case DATAFORMAT_SNORM8_VEC4: return MTLPixelFormatRGBA8Snorm;
case DATAFORMAT_INT8_VEC4: return MTLPixelFormatRGBA8Sint;
case DATAFORMAT_UINT8_VEC4: return MTLPixelFormatRGBA8Uint;
case DATAFORMAT_UNORM16_VEC2: return MTLPixelFormatRG16Unorm;
case DATAFORMAT_UNORM16_VEC4: return MTLPixelFormatRGBA16Unorm;
case DATAFORMAT_INT16_VEC2: return MTLPixelFormatRG16Sint;
case DATAFORMAT_INT16_VEC4: return MTLPixelFormatRGBA16Sint;
case DATAFORMAT_UINT16: return MTLPixelFormatR16Uint;
case DATAFORMAT_UINT16_VEC2: return MTLPixelFormatRG16Uint;
case DATAFORMAT_UINT16_VEC4: return MTLPixelFormatRGBA16Uint;
default: return MTLPixelFormatInvalid;
}
}
Buffer::Buffer(love::graphics::Graphics *gfx, id<MTLDevice> device, const Settings &settings, const std::vector<DataDeclaration> &format, const void *data, size_t size, size_t arraylength)
: love::graphics::Buffer(gfx, settings, format, size, arraylength)
, texture(nil)
, mapBuffer(nil)
, mappedRange()
{ @autoreleasepool {
size = getSize();
arraylength = getArrayLength();
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 with %d bytes (out of VRAM?)", size);
if (usageFlags & BUFFERUSAGEFLAG_TEXEL)
{
if (@available(iOS 12, macOS 10.14, *))
{
// TODO: minimumTextureBufferAlignmentForPixelFormat
MTLPixelFormat pixformat = getMTLPixelFormat(getDataMember(0).decl.format);
if (pixformat == MTLPixelFormatInvalid)
throw love::Exception("Could not create Metal texel buffer: invalid format.");
size_t width = arraylength * getDataMembers().size();
auto desc = [MTLTextureDescriptor textureBufferDescriptorWithPixelFormat:pixformat
width:width
resourceOptions:opts
usage:MTLTextureUsageShaderRead];
texture = [buffer newTextureWithDescriptor:desc offset:0 bytesPerRow:size];
}
if (texture == nil)
throw love::Exception("Could not create Metal texel buffer.");
}
if (data != nullptr)
fill(0, size, data);
else if (settings.zeroInitialize)
{
auto *mgfx = (Graphics *) gfx;
auto encoder = mgfx->useBlitEncoder();
size_t clearsize = size;
#ifdef LOVE_MACOS
// Metal limitation on macOS.
clearsize -= (clearsize % 4);
#endif
if (clearsize > 0)
[encoder fillBuffer:buffer range:NSMakeRange(0, clearsize) value:0];
}
}}
Buffer::~Buffer()
{ @autoreleasepool {
buffer = nil;
texture = nil;
}}
void *Buffer::map(MapType map, size_t offset, size_t size)
{ @autoreleasepool {
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
// buffers.
mapBuffer = [gfx->device newBufferWithLength:size options:MTLResourceStorageModeShared];
if (mapBuffer != nil)
{
mappedRange = r;
mapped = true;
mappedType = map;
return mapBuffer.contents;
}
return nullptr;
}}
void Buffer::unmap(size_t usedoffset, size_t usedsize)
{ @autoreleasepool {
if (mappedType == MAP_READ_ONLY)
{
mapped = false;
return;
}
if (mapBuffer == nil)
return;
Range r(usedoffset, usedsize);
if (!mapped || !mappedRange.contains(r))
return;
auto gfx = Graphics::getInstance();
auto encoder = gfx->useBlitEncoder();
[encoder copyFromBuffer:mapBuffer
sourceOffset:(usedoffset - mappedRange.getOffset())
toBuffer:buffer
destinationOffset:usedoffset
size:usedsize];
mapBuffer = nil;
mapped = false;
}}
bool Buffer::fill(size_t offset, size_t size, const void *data)
{ @autoreleasepool {
void *dest = map(MAP_WRITE_INVALIDATE, offset, size);
if (dest == nullptr)
return false;
memcpy(dest, data, size);
unmap(offset, size);
return true;
}}
void Buffer::clear(size_t offset, size_t size)
{ @autoreleasepool {
if (isImmutable())
throw love::Exception("Cannot clear an immutable Buffer.");
else if (isMapped())
throw love::Exception("Cannot clear a mapped Buffer.");
else if (offset + size > getSize())
throw love::Exception("The given offset and size parameters to clear() are not within the Buffer's size.");
else if (offset % 4 != 0 || size % 4 != 0)
throw love::Exception("clear() must be used with offset and size parameters that are multiples of 4 bytes.");
auto gfx = Graphics::getInstance();
auto encoder = gfx->useBlitEncoder();
[encoder fillBuffer:buffer range:NSMakeRange(offset, size) value:0];
}}
void Buffer::copyTo(love::graphics::Buffer *dest, size_t sourceoffset, size_t destoffset, size_t size)
{ @autoreleasepool {
auto gfx = Graphics::getInstance();
auto encoder = gfx->useBlitEncoder();
[encoder copyFromBuffer:buffer
sourceOffset:sourceoffset
toBuffer:((Buffer *) dest)->buffer
destinationOffset:destoffset
size:size];
}}
} // metal
} // graphics
} // love
+264
View File
@@ -0,0 +1,264 @@
/**
* Copyright (c) 2006-2023 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
#include "graphics/Graphics.h"
#include "Metal.h"
#include "Shader.h"
#include <map>
@class CAMetalLayer;
@protocol CAMetalDrawable;
namespace love
{
namespace graphics
{
namespace metal
{
class Graphics final : public love::graphics::Graphics
{
public:
enum SubmitType
{
SUBMIT_DONE,
SUBMIT_STORE,
};
struct RenderEncoderBindings
{
void *textures[32][SHADERSTAGE_MAX_ENUM];
void *samplers[32][SHADERSTAGE_MAX_ENUM];
struct
{
void *buffer;
size_t offset;
} buffers[32][SHADERSTAGE_MAX_ENUM];
};
Graphics();
virtual ~Graphics();
// Implements Module.
const char *getName() const override { return "love.graphics.metal"; }
love::graphics::Texture *newTexture(const Texture::Settings &settings, const Texture::Slices *data = nullptr) override;
love::graphics::Buffer *newBuffer(const Buffer::Settings &settings, const std::vector<Buffer::DataDeclaration> &format, const void *data, size_t size, size_t arraylength) override;
Matrix4 computeDeviceProjection(const Matrix4 &projection, bool rendertotexture) const override;
void setViewportSize(int width, int height, int pixelwidth, int pixelheight) override;
bool setMode(void *context, int width, int height, int pixelwidth, int pixelheight, bool windowhasstencil, int msaa) override;
void unSetMode() override;
void setActive(bool active) override;
bool dispatch(love::graphics::Shader *shader, int x, int y, int z) override;
bool dispatch(love::graphics::Shader *shader, love::graphics::Buffer *indirectargs, size_t argsoffset) override;
void draw(const DrawCommand &cmd) override;
void draw(const DrawIndexedCommand &cmd) override;
void drawQuads(int start, int count, const VertexAttributes &attributes, const BufferBindings &buffers, love::graphics::Texture *texture) override;
void clear(OptionalColorD color, OptionalInt stencil, OptionalDouble depth) override;
void clear(const std::vector<OptionalColorD> &colors, OptionalInt stencil, OptionalDouble depth) override;
void discard(const std::vector<bool> &colorbuffers, bool depthstencil) override;
void present(void *screenshotCallbackData) override;
int getRequestedBackbufferMSAA() const override;
int getBackbufferMSAA() const override;
void setColor(Colorf c) override;
void setScissor(const Rect &rect) override;
void setScissor() override;
void setStencilMode(StencilAction action, CompareMode compare, int value, uint32 readmask, uint32 writemask) override;
void setDepthMode(CompareMode compare, bool write) override;
void setFrontFaceWinding(Winding winding) override;
void setColorMask(ColorChannelMask mask) override;
void setBlendState(const BlendState &state) override;
void setPointSize(float size) override;
void setWireframe(bool enable) override;
PixelFormat getSizedFormat(PixelFormat format, bool rendertarget, bool readable) const override;
bool isPixelFormatSupported(PixelFormat format, uint32 usage, bool sRGB = false) override;
Renderer getRenderer() const override;
bool usesGLSLES() const override;
RendererInfo getRendererInfo() const override;
void setShaderChanged();
id<MTLCommandBuffer> useCommandBuffer();
id<MTLCommandBuffer> getCommandBuffer() const { return commandBuffer; }
void submitCommandBuffer(SubmitType type);
void submitAllEncoders(SubmitType type);
id<MTLRenderCommandEncoder> useRenderEncoder();
id<MTLRenderCommandEncoder> getRenderEncoder() const { return renderEncoder; }
void submitRenderEncoder(SubmitType type);
id<MTLBlitCommandEncoder> useBlitEncoder();
id<MTLBlitCommandEncoder> getBlitEncoder() const { return blitEncoder; }
void submitBlitEncoder();
id<MTLComputeCommandEncoder> useComputeEncoder();
id<MTLComputeCommandEncoder> getComputeEncoder() const { return computeEncoder; }
void submitComputeEncoder();
id<MTLSamplerState> getCachedSampler(const SamplerState &s);
StreamBuffer *getUniformBuffer() const { return uniformBuffer; }
Buffer *getDefaultAttributesBuffer() const { return defaultAttributesBuffer; }
Texture *getDefaultTexture(TextureType textype) const { return defaultTextures[textype]; }
int getClosestMSAASamples(int requestedsamples);
static Graphics *getInstance() { return graphicsInstance; }
id<MTLDevice> device;
private:
static Graphics *graphicsInstance;
enum StateType
{
STATE_BLEND,
STATE_VIEWPORT,
STATE_SCISSOR,
STATE_STENCIL,
STATE_DEPTH,
STATE_SHADER,
STATE_COLORMASK,
STATE_CULLMODE,
STATE_FACEWINDING,
STATE_WIREFRAME,
};
enum StateBit
{
STATEBIT_BLEND = 1 << STATE_BLEND,
STATEBIT_VIEWPORT = 1 << STATE_VIEWPORT,
STATEBIT_SCISSOR = 1 << STATE_SCISSOR,
STATEBIT_STENCIL = 1 << STATE_STENCIL,
STATEBIT_DEPTH = 1 << STATE_DEPTH,
STATEBIT_SHADER = 1 << STATE_SHADER,
STATEBIT_COLORMASK = 1 << STATE_COLORMASK,
STATEBIT_CULLMODE = 1 << STATE_CULLMODE,
STATEBIT_FACEWINDING = 1 << STATE_FACEWINDING,
STATEBIT_WIREFRAME = 1 << STATE_WIREFRAME,
STATEBIT_ALL = 0xFFFFFFFF
};
struct DeviceFamilies
{
// All arrays are 1-indexed for convenience
bool apple[7+1];
bool mac[2+1];
bool common[3+1];
bool macCatalyst[2+1];
};
struct AttachmentStoreActions
{
MTLStoreAction color[MAX_COLOR_RENDER_TARGETS];
MTLStoreAction depth;
MTLStoreAction stencil;
};
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;
void processCompletedCommandBuffers();
void endPass(bool presenting);
id<MTLDepthStencilState> getCachedDepthStencilState(const DepthState &depth, const StencilState &stencil);
void applyRenderState(id<MTLRenderCommandEncoder> renderEncoder, const VertexAttributes &attributes);
bool applyShaderUniforms(id<MTLComputeCommandEncoder> encoder, love::graphics::Shader *shader);
void applyShaderUniforms(id<MTLRenderCommandEncoder> renderEncoder, love::graphics::Shader *shader, Texture *maintex);
id<MTLCommandQueue> commandQueue;
id<MTLCommandBuffer> commandBuffer;
id<MTLRenderCommandEncoder> renderEncoder;
id<MTLBlitCommandEncoder> blitEncoder;
id<MTLComputeCommandEncoder> computeEncoder;
CAMetalLayer *metalLayer;
id<CAMetalDrawable> activeDrawable;
MTLRenderPassDescriptor *passDesc;
uint32 dirtyRenderState;
CullMode lastCullMode;
Shader::RenderPipelineKey lastRenderPipelineKey;
bool windowHasStencil;
int shaderSwitches;
StrongRef<love::graphics::Texture> backbufferMSAA;
StrongRef<love::graphics::Texture> backbufferDepthStencil;
int requestedBackbufferMSAA;
AttachmentStoreActions attachmentStoreActions;
RenderEncoderBindings renderBindings;
StreamBuffer *uniformBuffer;
StreamBuffer::MapInfo uniformBufferData;
size_t uniformBufferOffset;
Buffer *defaultAttributesBuffer;
Texture *defaultTextures[TEXTURE_MAX_ENUM];
std::map<uint64, void *> cachedSamplers;
std::unordered_map<uint64, void *> cachedDepthStencilStates;
std::vector<id<MTLCommandBuffer>> activeCommandBuffers;
DeviceFamilies families;
}; // Graphics
} // metal
} // graphics
} // love

Some files were not shown because too many files have changed in this diff Show More