mirror of
https://github.com/love2d/love.git
synced 2026-08-16 00:02:12 +02:00
Merge branch 'sdf-font-hintingmode' of https://github.com/Labrium/love into sdf-font-hintingmode
This commit is contained in:
@@ -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},
|
||||
|
||||
@@ -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[];
|
||||
|
||||
@@ -211,6 +211,15 @@ void Audio::resumeContext()
|
||||
{
|
||||
}
|
||||
|
||||
std::string Audio::getPlaybackDevice()
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
void Audio::getPlaybackDevices(std::vector<std::string> &/*list*/)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
} // null
|
||||
} // audio
|
||||
|
||||
@@ -89,6 +89,9 @@ public:
|
||||
void pauseContext();
|
||||
void resumeContext();
|
||||
|
||||
std::string getPlaybackDevice();
|
||||
void getPlaybackDevices(std::vector<std::string> &list);
|
||||
|
||||
private:
|
||||
float volume;
|
||||
DistanceModel distanceModel;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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 }
|
||||
};
|
||||
|
||||
@@ -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 }
|
||||
};
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
|
||||
// LOVE
|
||||
#include "CompressedData.h"
|
||||
#include "common/Exception.h"
|
||||
|
||||
namespace love
|
||||
{
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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 }
|
||||
};
|
||||
|
||||
|
||||
@@ -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 }
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
};
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
// LOVE
|
||||
#include "common/config.h"
|
||||
#include "Event.h"
|
||||
#include "common/runtime.h"
|
||||
|
||||
namespace love
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
+5
-5
@@ -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
|
||||
@@ -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();
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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.
|
||||
|
||||
+195
-534
File diff suppressed because it is too large
Load Diff
+23
-61
@@ -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;
|
||||
|
||||
|
||||
+1169
-434
File diff suppressed because it is too large
Load Diff
+255
-259
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -673,7 +673,7 @@ private:
|
||||
|
||||
bool relativeRotation;
|
||||
|
||||
const vertex::Attributes vertexAttributes;
|
||||
const VertexAttributes vertexAttributes;
|
||||
Buffer *buffer;
|
||||
|
||||
static StringMap<AreaSpreadDistribution, DISTRIBUTION_MAX_ENUM>::Entry distributionsEntries[];
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
+1199
-46
File diff suppressed because it is too large
Load Diff
+119
-27
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
+725
-155
File diff suppressed because it is too large
Load Diff
+201
-65
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
Reference in New Issue
Block a user