Browse Source

Merge branch '12.0-development' into metal

Alex Szpakowski 4 years ago
parent
commit
2d2918a254
51 changed files with 1417 additions and 830 deletions
  1. 38 29
      CMakeLists.txt
  2. 5 0
      src/common/Range.h
  3. 401 3
      src/common/android.cpp
  4. 21 0
      src/common/android.h
  5. 52 12
      src/common/runtime.cpp
  6. 49 19
      src/modules/filesystem/physfs/Filesystem.cpp
  7. 10 6
      src/modules/graphics/Buffer.cpp
  8. 15 20
      src/modules/graphics/Buffer.h
  9. 88 163
      src/modules/graphics/Graphics.cpp
  10. 15 51
      src/modules/graphics/Graphics.h
  11. 14 14
      src/modules/graphics/Mesh.cpp
  12. 2 2
      src/modules/graphics/Mesh.h
  13. 3 3
      src/modules/graphics/ParticleSystem.cpp
  14. 49 25
      src/modules/graphics/Shader.cpp
  15. 3 3
      src/modules/graphics/Shader.h
  16. 8 8
      src/modules/graphics/SpriteBatch.cpp
  17. 1 1
      src/modules/graphics/SpriteBatch.h
  18. 1 1
      src/modules/graphics/StreamBuffer.cpp
  19. 3 3
      src/modules/graphics/StreamBuffer.h
  20. 7 3
      src/modules/graphics/Text.cpp
  21. 3 3
      src/modules/graphics/Texture.cpp
  22. 21 1
      src/modules/graphics/Texture.h
  23. 1 0
      src/modules/graphics/metal/Buffer.h
  24. 13 1
      src/modules/graphics/metal/Buffer.mm
  25. 2 2
      src/modules/graphics/metal/Graphics.h
  26. 11 10
      src/modules/graphics/metal/Graphics.mm
  27. 1 1
      src/modules/graphics/metal/StreamBuffer.h
  28. 4 4
      src/modules/graphics/metal/StreamBuffer.mm
  29. 1 1
      src/modules/graphics/metal/Texture.h
  30. 1 1
      src/modules/graphics/metal/Texture.mm
  31. 36 22
      src/modules/graphics/opengl/Buffer.cpp
  32. 2 1
      src/modules/graphics/opengl/Buffer.h
  33. 44 27
      src/modules/graphics/opengl/Graphics.cpp
  34. 3 3
      src/modules/graphics/opengl/Graphics.h
  35. 162 152
      src/modules/graphics/opengl/OpenGL.cpp
  36. 8 15
      src/modules/graphics/opengl/OpenGL.h
  37. 12 24
      src/modules/graphics/opengl/Shader.cpp
  38. 0 7
      src/modules/graphics/opengl/Shader.h
  39. 7 7
      src/modules/graphics/opengl/StreamBuffer.cpp
  40. 1 1
      src/modules/graphics/opengl/StreamBuffer.h
  41. 5 13
      src/modules/graphics/opengl/Texture.cpp
  42. 1 1
      src/modules/graphics/opengl/Texture.h
  43. 13 11
      src/modules/graphics/vertex.cpp
  44. 28 14
      src/modules/graphics/vertex.h
  45. 41 22
      src/modules/graphics/wrap_Buffer.cpp
  46. 36 13
      src/modules/graphics/wrap_Graphics.cpp
  47. 57 82
      src/modules/joystick/Joystick.cpp
  48. 29 25
      src/modules/joystick/Joystick.h
  49. 56 0
      src/modules/joystick/sdl/Joystick.cpp
  50. 5 0
      src/modules/joystick/sdl/Joystick.h
  51. 28 0
      src/modules/joystick/wrap_Joystick.cpp

+ 38 - 29
CMakeLists.txt

@@ -145,10 +145,10 @@ if(MEGA)
 		# automatically.
 		# automatically.
 	endif()
 	endif()
 else()
 else()
-	if(MSVC)
+	if(MSVC or ANDROID)
 		message(FATAL_ERROR "
 		message(FATAL_ERROR "
-It is currently only possible to build with megasource on Windows.
-Please see http://bitbucket.org/rude/megasource
+It is currently only possible to build with megasource on Windows and Android.
+Please see https://github.com/love2d/megasource
 ")
 ")
 	endif()
 	endif()
 
 
@@ -1696,6 +1696,13 @@ if(MSVC)
 	)
 	)
 endif()
 endif()
 
 
+if(ANDROID)
+	# In Android, the LOVE main entrypoint needs to be compiled
+	# as shared library, so change the library name and add love.cpp
+	set(LOVE_LIB_NAME ${LOVE_EXE_NAME})
+	set(LOVE_LIB_SRC ${LOVE_LIB_SRC} src/love.cpp)
+endif()
+
 add_library(${LOVE_LIB_NAME} SHARED ${LOVE_LIB_SRC} ${LOVE_RC})
 add_library(${LOVE_LIB_NAME} SHARED ${LOVE_LIB_SRC} ${LOVE_RC})
 target_link_libraries(${LOVE_LIB_NAME} ${LOVE_LINK_LIBRARIES} ${LOVE_3P})
 target_link_libraries(${LOVE_LIB_NAME} ${LOVE_LINK_LIBRARIES} ${LOVE_3P})
 
 
@@ -1711,35 +1718,37 @@ endif()
 #
 #
 # love (executable)
 # love (executable)
 #
 #
-add_executable(${LOVE_EXE_NAME} WIN32 src/love.cpp ${LOVE_RC})
-target_link_libraries(${LOVE_EXE_NAME} ${LOVE_LIB_NAME})
-
-if(MSVC)
-	add_executable(${LOVE_CONSOLE_EXE_NAME} src/love.cpp ${LOVE_RC})
-	target_link_libraries(${LOVE_CONSOLE_EXE_NAME} ${LOVE_LIB_NAME})
-endif()
+if(NOT ANDROID)
+	add_executable(${LOVE_EXE_NAME} WIN32 src/love.cpp ${LOVE_RC})
+	target_link_libraries(${LOVE_EXE_NAME} ${LOVE_LIB_NAME})
 
 
-function(post_step_move_dll ARG_POST_TARGET ARG_TARGET_OR_FILE)
-	if(TARGET ${ARG_TARGET_OR_FILE})
-		add_custom_command(TARGET ${ARG_POST_TARGET} POST_BUILD
-			COMMAND ${CMAKE_COMMAND} -E copy
-			$<TARGET_FILE:${ARG_TARGET_OR_FILE}>
-			${CMAKE_CURRENT_BINARY_DIR}/$<CONFIGURATION>/$<TARGET_FILE_NAME:${ARG_TARGET_OR_FILE}>)
-	else()
-		get_filename_component(TEMP_FILENAME ${ARG_TARGET_OR_FILE} NAME)
-		add_custom_command(TARGET ${ARG_POST_TARGET} POST_BUILD
-			COMMAND ${CMAKE_COMMAND} -E copy
-			${ARG_TARGET_OR_FILE}
-			${CMAKE_CURRENT_BINARY_DIR}/$<CONFIGURATION>/${TEMP_FILENAME})
+	if(MSVC)
+		add_executable(${LOVE_CONSOLE_EXE_NAME} src/love.cpp ${LOVE_RC})
+		target_link_libraries(${LOVE_CONSOLE_EXE_NAME} ${LOVE_LIB_NAME})
 	endif()
 	endif()
-endfunction()
 
 
-# Add post build steps to move the DLLs next to the binary. Otherwise
-# running/debugging the binary will not work from inside VS.
-if(LOVE_MOVE_DLLS)
-	foreach(DLL ${LOVE_MOVE_DLLS})
-		post_step_move_dll(love ${DLL})
-	endforeach()
+	function(post_step_move_dll ARG_POST_TARGET ARG_TARGET_OR_FILE)
+		if(TARGET ${ARG_TARGET_OR_FILE})
+			add_custom_command(TARGET ${ARG_POST_TARGET} POST_BUILD
+				COMMAND ${CMAKE_COMMAND} -E copy
+				$<TARGET_FILE:${ARG_TARGET_OR_FILE}>
+				${CMAKE_CURRENT_BINARY_DIR}/$<CONFIGURATION>/$<TARGET_FILE_NAME:${ARG_TARGET_OR_FILE}>)
+		else()
+			get_filename_component(TEMP_FILENAME ${ARG_TARGET_OR_FILE} NAME)
+			add_custom_command(TARGET ${ARG_POST_TARGET} POST_BUILD
+				COMMAND ${CMAKE_COMMAND} -E copy
+				${ARG_TARGET_OR_FILE}
+				${CMAKE_CURRENT_BINARY_DIR}/$<CONFIGURATION>/${TEMP_FILENAME})
+		endif()
+	endfunction()
+
+	# Add post build steps to move the DLLs next to the binary. Otherwise
+	# running/debugging the binary will not work from inside VS.
+	if(LOVE_MOVE_DLLS)
+		foreach(DLL ${LOVE_MOVE_DLLS})
+			post_step_move_dll(love ${DLL})
+		endforeach()
+	endif()
 endif()
 endif()
 
 
 if (NOT MSVC)
 if (NOT MSVC)

+ 5 - 0
src/common/Range.h

@@ -61,6 +61,11 @@ struct Range
 		return first <= other.first && last >= other.last;
 		return first <= other.first && last >= other.last;
 	}
 	}
 
 
+	bool intersects(const Range &other)
+	{
+		return !(first > other.last || last < other.first);
+	}
+
 	void encapsulate(size_t index)
 	void encapsulate(size_t index)
 	{
 	{
 		first = std::min(first, index);
 		first = std::min(first, index);

+ 401 - 3
src/common/android.cpp

@@ -22,13 +22,20 @@
 
 
 #ifdef LOVE_ANDROID
 #ifdef LOVE_ANDROID
 
 
-#include "SDL.h"
-#include "jni.h"
+#include <cerrno>
+#include <unordered_map>
+
+#include <SDL.h>
+
+#include <jni.h>
+#include <android/asset_manager.h>
+#include <android/asset_manager_jni.h>
 
 
 #include <sys/stat.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <sys/types.h>
 #include <unistd.h>
 #include <unistd.h>
-#include <errno.h>
+
+#include "physfs.h"
 
 
 namespace love
 namespace love
 {
 {
@@ -326,6 +333,397 @@ void showRecordingPermissionMissingDialog()
 	env->DeleteLocalRef(activity);
 	env->DeleteLocalRef(activity);
 }
 }
 
 
+/*
+ * Helper functions to aid new fusing method
+ */
+static AAssetManager *getAssetManager()
+{
+	JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv();
+	jobject self = (jobject) SDL_AndroidGetActivity();
+	jclass activity = env->GetObjectClass(self);
+	jmethodID method = env->GetMethodID(activity, "getAssetManager", "()Landroid/content/res/AssetManager;");
+
+	jobject assetManager = env->CallObjectMethod(self, method);
+	AAssetManager *assetManagerObject = AAssetManager_fromJava(env, assetManager);
+
+	env->DeleteLocalRef(assetManager);
+	env->DeleteLocalRef(activity);
+	env->DeleteLocalRef(self);
+
+	return assetManagerObject;
+}
+
+namespace aasset
+{
+namespace io
+{
+
+struct AssetInfo
+{
+	AAssetManager *assetManager;
+	AAsset *asset;
+	char *filename;
+	size_t size;
+};
+
+static std::unordered_map<std::string, PHYSFS_FileType> fileTree;
+
+// Workaround AAsset can't detect whetever something is a directory or doesn't exist
+static void buildFileLookup(
+	AAssetManager *assetManager,
+	std::unordered_map<std::string, PHYSFS_FileType> &out,
+	const std::string &path = ""
+)
+{
+	if (!path.empty())
+	{
+		AAsset *test = AAssetManager_open(assetManager, path.c_str(), AASSET_MODE_STREAMING);
+		if (test)
+		{
+			AAsset_close(test);
+			out[path] = PHYSFS_FILETYPE_REGULAR;
+			return;
+		}
+
+		out[path] = PHYSFS_FILETYPE_DIRECTORY;
+	}
+
+	AAssetDir *dir = AAssetManager_openDir(assetManager, path.c_str());
+	const char *file;
+
+	while ((file = AAssetDir_getNextFileName(dir)) != nullptr)
+		buildFileLookup(assetManager, out, file);
+
+	AAssetDir_close(dir);
+}
+
+PHYSFS_sint64 read(PHYSFS_Io *io, void *buf, PHYSFS_uint64 len)
+{
+	AAsset *asset = ((AssetInfo *) io->opaque)->asset;
+	int readed = AAsset_read(asset, buf, (size_t) len);
+
+	PHYSFS_setErrorCode(readed < 0 ? PHYSFS_ERR_OS_ERROR : PHYSFS_ERR_OK);
+	return (PHYSFS_sint64) readed;
+}
+
+PHYSFS_sint64 write(PHYSFS_Io *io, const void *buf, PHYSFS_uint64 len)
+{
+	LOVE_UNUSED(io);
+	LOVE_UNUSED(buf);
+	LOVE_UNUSED(len);
+
+	PHYSFS_setErrorCode(PHYSFS_ERR_READ_ONLY);
+	return -1;
+}
+
+int seek(PHYSFS_Io *io, PHYSFS_uint64 offset)
+{
+	AAsset *asset = ((AssetInfo *) io->opaque)->asset;
+	int success = AAsset_seek64(asset, (off64_t) offset, SEEK_SET) != -1;
+
+	PHYSFS_setErrorCode(success ? PHYSFS_ERR_OK : PHYSFS_ERR_OS_ERROR);
+	return success;
+}
+
+PHYSFS_sint64 tell(PHYSFS_Io *io)
+{
+	AAsset *asset = ((AssetInfo *) io->opaque)->asset;
+	off64_t len = AAsset_getLength64(asset);
+	off64_t remain = AAsset_getRemainingLength64(asset);
+
+	return len - remain;
+}
+
+PHYSFS_sint64 length(PHYSFS_Io *io)
+{
+	AAsset *asset = ((AssetInfo *) io->opaque)->asset;
+	return AAsset_getLength64(asset);
+}
+
+// Forward declaration
+PHYSFS_Io *fromAAsset(AAssetManager *assetManager, const char *filename, AAsset *asset);
+
+PHYSFS_Io *duplicate(PHYSFS_Io *io)
+{
+	AssetInfo *assetInfo = (AssetInfo *) io->opaque;
+	AAsset *asset = AAssetManager_open(assetInfo->assetManager, assetInfo->filename, AASSET_MODE_RANDOM);
+
+	if (asset == nullptr)
+	{
+		PHYSFS_setErrorCode(PHYSFS_ERR_OS_ERROR);
+		return nullptr;
+	}
+
+	AAsset_seek64(asset, tell(io), SEEK_SET);
+	return fromAAsset(assetInfo->assetManager, assetInfo->filename, asset);
+}
+
+void destroy(PHYSFS_Io *io)
+{
+	AssetInfo *assetInfo = (AssetInfo *) io->opaque;
+	AAsset_close(assetInfo->asset);
+	delete[] assetInfo->filename;
+	delete assetInfo;
+	delete io;
+}
+
+PHYSFS_Io *fromAAsset(AAssetManager *assetManager, const char *filename, AAsset *asset)
+{
+	// Create AssetInfo
+	AssetInfo *assetInfo = new (std::nothrow) AssetInfo();
+	assetInfo->assetManager = assetManager;
+	assetInfo->asset = asset;
+	assetInfo->size = strlen(filename) + 1;
+	assetInfo->filename = new (std::nothrow) char[assetInfo->size];
+	memcpy(assetInfo->filename, filename, assetInfo->size);
+
+	// Create PHYSFS_Io
+	PHYSFS_Io *io = new (std::nothrow) PHYSFS_Io();
+	io->version = 0;
+	io->opaque = assetInfo;
+	io->read = read;
+	io->write = write;
+	io->seek = seek;
+	io->tell = tell;
+	io->length = length;
+	io->duplicate = duplicate;
+	io->flush = nullptr;
+	io->destroy = destroy;
+
+	return io;
+}
+
+}
+
+void *openArchive(PHYSFS_Io *io, const char *name, int forWrite, int *claimed)
+{
+	if (io->opaque == nullptr || memcmp(io->opaque, "ASET", 4) != 0)
+		return nullptr;
+
+	// It's our archive
+	*claimed = 1;
+	AAssetManager *assetManager = getAssetManager();
+
+	if (io::fileTree.empty())
+		io::buildFileLookup(assetManager, io::fileTree);
+
+	return assetManager;
+}
+
+PHYSFS_EnumerateCallbackResult enumerate(
+	void *opaque,
+	const char *dirname,
+	PHYSFS_EnumerateCallback cb,
+	const char *origdir,
+	void *callbackdata
+)
+{
+	const char *path = dirname;
+	if (path == nullptr || (path[0] == '/' && path[1] == 0))
+		path = "";
+
+	AAssetManager *assetManager = (AAssetManager *) opaque;
+	AAssetDir *dir = AAssetManager_openDir(assetManager, path);
+
+	if (dir == nullptr)
+	{
+		PHYSFS_setErrorCode(PHYSFS_ERR_NOT_FOUND);
+		return PHYSFS_ENUM_ERROR;
+	}
+
+	PHYSFS_EnumerateCallbackResult ret = PHYSFS_ENUM_OK;
+
+	while (ret == PHYSFS_ENUM_OK)
+	{
+		const char *name = AAssetDir_getNextFileName(dir);
+
+		// No more files?
+		if (name == nullptr)
+			break;
+
+		ret = cb(callbackdata, origdir, name);
+	}
+
+	AAssetDir_close(dir);
+	return ret;
+}
+
+PHYSFS_Io *openRead(void *opaque, const char *name)
+{
+	AAssetManager *assetManager = (AAssetManager *) opaque;
+	AAsset *file = AAssetManager_open(assetManager, name, AASSET_MODE_UNKNOWN);
+
+	if (file == nullptr)
+	{
+		PHYSFS_setErrorCode(PHYSFS_ERR_NOT_FOUND);
+		return nullptr;
+	}
+
+	PHYSFS_setErrorCode(PHYSFS_ERR_OK);
+	return io::fromAAsset(assetManager, name, file);
+}
+
+PHYSFS_Io *openWriteAppend(void *opaque, const char *name)
+{
+	LOVE_UNUSED(opaque);
+	LOVE_UNUSED(name);
+
+	// AAsset doesn't support modification
+	PHYSFS_setErrorCode(PHYSFS_ERR_READ_ONLY);
+	return nullptr;
+}
+
+int removeMkdir(void *opaque, const char *name)
+{
+	LOVE_UNUSED(opaque);
+	LOVE_UNUSED(name);
+
+	// AAsset doesn't support modification
+	PHYSFS_setErrorCode(PHYSFS_ERR_READ_ONLY);
+	return 0;
+}
+
+int stat(void *opaque, const char *name, PHYSFS_Stat *out)
+{
+	LOVE_UNUSED(opaque);
+
+	auto result = io::fileTree.find(name);
+
+	if (result != io::fileTree.end())
+	{
+		out->filetype = result->second;
+		out->modtime = -1;
+		out->createtime = -1;
+		out->accesstime = -1;
+		out->readonly = 1;
+
+		PHYSFS_setErrorCode(PHYSFS_ERR_OK);
+		return 1;
+	}
+	else
+	{
+		PHYSFS_setErrorCode(PHYSFS_ERR_NOT_FOUND);
+		return 0;
+	}
+}
+
+void closeArchive(void *opaque)
+{
+	// Do nothing
+	LOVE_UNUSED(opaque);
+	PHYSFS_setErrorCode(PHYSFS_ERR_OK);
+}
+
+static PHYSFS_Archiver g_AAssetArchiver = {
+	0,
+	{
+		"AASSET",
+		"Android AAsset Wrapper",
+		"LOVE Development Team",
+		"https://developer.android.com/ndk/reference/group/asset",
+		0
+	},
+	openArchive,
+	enumerate,
+	openRead,
+	openWriteAppend,
+	openWriteAppend,
+	removeMkdir,
+	removeMkdir,
+	stat,
+	closeArchive
+};
+
+static PHYSFS_sint64 dummyReturn0(PHYSFS_Io *io)
+{
+	LOVE_UNUSED(io);
+	PHYSFS_setErrorCode(PHYSFS_ERR_OK);
+	return 0;
+}
+
+static PHYSFS_Io *getDummyIO(PHYSFS_Io *io);
+
+static char dummyOpaque[] = "ASET";
+static PHYSFS_Io dummyIo = {
+	0,
+	dummyOpaque,
+	nullptr,
+	nullptr,
+	[](PHYSFS_Io *io, PHYSFS_uint64 offset) -> int
+	{
+		PHYSFS_setErrorCode(offset == 0 ? PHYSFS_ERR_OK : PHYSFS_ERR_PAST_EOF);
+		return offset == 0;
+	},
+	dummyReturn0,
+	dummyReturn0,
+	getDummyIO,
+	nullptr,
+	[](PHYSFS_Io *io) { LOVE_UNUSED(io); }
+};
+
+static PHYSFS_Io *getDummyIO(PHYSFS_Io *io)
+{
+	return &dummyIo;
+}
+
+}
+
+static bool isVirtualArchiveInitialized = false;
+
+bool initializeVirtualArchive()
+{
+	if (isVirtualArchiveInitialized)
+		return true;
+
+	if (!PHYSFS_registerArchiver(&aasset::g_AAssetArchiver))
+		return false;
+	if (!PHYSFS_mountIo(&aasset::dummyIo, "ASET.AASSET", nullptr, 0))
+	{
+		PHYSFS_deregisterArchiver(aasset::g_AAssetArchiver.info.extension);
+		return false;
+	}
+
+	isVirtualArchiveInitialized = true;
+	return true;
+}
+
+void deinitializeVirtualArchive()
+{
+	if (isVirtualArchiveInitialized)
+	{
+		PHYSFS_deregisterArchiver(aasset::g_AAssetArchiver.info.extension);
+		isVirtualArchiveInitialized = false;
+	}
+}
+
+bool checkFusedGame(void **physfsIO_Out)
+{
+	// TODO: Reorder the loading in 12.0
+	PHYSFS_Io *&io = *(PHYSFS_Io **) physfsIO_Out;
+	AAssetManager *assetManager = getAssetManager();
+
+	// Prefer game.love inside assets/ folder
+	AAsset *asset = AAssetManager_open(assetManager, "game.love", AASSET_MODE_RANDOM);
+	if (asset)
+	{
+		io = aasset::io::fromAAsset(assetManager, "game.love", asset);
+		return true;
+	}
+
+	// If there's no game.love inside assets/ try main.lua
+	asset = AAssetManager_open(assetManager, "main.lua", AASSET_MODE_STREAMING);
+
+	if (asset)
+	{
+		AAsset_close(asset);
+		io = nullptr;
+		return true;
+	}
+
+	// Not found
+	return false;
+}
+
 } // android
 } // android
 } // love
 } // love
 
 

+ 21 - 0
src/common/android.h

@@ -80,6 +80,27 @@ void requestRecordingPermission();
 
 
 void showRecordingPermissionMissingDialog();
 void showRecordingPermissionMissingDialog();
 
 
+/**
+ * Initialize Android AAsset virtual archive.
+ * @return true if successful.
+ */
+bool initializeVirtualArchive();
+
+/**
+ * Deinitialize Android AAsset virtual archive.
+ * @return true if successful.
+ */
+void deinitializeVirtualArchive();
+
+/**
+ * Retrieve the fused game inside the APK
+ * @param physfsIO_Out Pointer to PHYSFS_Io* struct
+ * @return true if there's game inside the APK. If physfsIO_Out is not null, then it contains
+ * the game.love which needs to be mounted to root. false if it's not fused, in which case
+ * physfsIO_Out is undefined.
+ */
+bool checkFusedGame(void **physfsIO_Out);
+
 } // android
 } // android
 } // love
 } // love
 
 

+ 52 - 12
src/common/runtime.cpp

@@ -93,11 +93,42 @@ static int w__eq(lua_State *L)
 	return 1;
 	return 1;
 }
 }
 
 
+typedef uint64 ObjectKey;
+
+static bool luax_isfulllightuserdatasupported(lua_State *L)
+{
+	// LuaJIT prior to commit e9af1abec542e6f9851ff2368e7f196b6382a44c doesn't
+	// support lightuserdata > 48 bits. This is not a problem with Android,
+	// Windows, macOS, and iOS as they'll use updated LuaJIT or won't use
+	// pointers > 48 bits, but this is not the case for Linux. So check for
+	// this capability first!
+	static bool checked = false;
+	static bool supported = false;
+
+	if (!checked)
+	{
+		lua_pushcclosure(L, [](lua_State *L) -> int
+		{
+			// Try to push pointer with all bits set.
+			lua_pushlightuserdata(L, (void *) (~((size_t) 0)));
+			return 1;
+		}, 0);
+
+		supported = lua_pcall(L, 0, 1, 0) == 0;
+		checked = true;
+
+		lua_pop(L, 1);
+	}
+
+	return supported;
+}
+
 // For use with the love object pointer -> Proxy pointer registry.
 // For use with the love object pointer -> Proxy pointer registry.
 // Using the pointer directly via lightuserdata would be ideal, but LuaJIT
 // Using the pointer directly via lightuserdata would be ideal, but LuaJIT
-// cannot use lightuserdata with more than 47 bits whereas some newer arm64
-// architectures allow pointers which use more than that.
-static lua_Number luax_computeloveobjectkey(lua_State *L, love::Object *object)
+// (before a commit to 2.1 in 2020) cannot use lightuserdata with more than 47
+// bits whereas some newer arm64 architectures allow pointers which use more
+// than that.
+static ObjectKey luax_computeloveobjectkey(lua_State *L, love::Object *object)
 {
 {
 	// love objects should be allocated on the heap, and thus are subject
 	// love objects should be allocated on the heap, and thus are subject
 	// to the alignment rules of operator new / malloc. Lua numbers (doubles)
 	// to the alignment rules of operator new / malloc. Lua numbers (doubles)
@@ -118,11 +149,20 @@ static lua_Number luax_computeloveobjectkey(lua_State *L, love::Object *object)
 
 
 	key >>= shift;
 	key >>= shift;
 
 
-	// Make sure our key isn't larger than 2^53.
-	if (key > 0x20000000000000ULL)
-		luaL_error(L, "Cannot push love object to Lua: pointer value %p is too large", object);
+	return (ObjectKey) key;
+}
 
 
-	return (lua_Number) key;
+static void luax_pushloveobjectkey(lua_State *L, ObjectKey key)
+{
+	// If full 64-bit lightuserdata is supported, always use that. Otherwise,
+	// if the key is smaller than 2^53 (which is integer precision for double
+	// datatype), then push number. Otherwise, throw error.
+	if (luax_isfulllightuserdatasupported(L))
+		lua_pushlightuserdata(L, (void *) key);
+	else if (key > 0x20000000000000ULL) // 2^53
+		luaL_error(L, "Cannot push love object to Lua: pointer value %p is too large", key);
+	else
+		lua_pushnumber(L, (lua_Number) key);
 }
 }
 
 
 static int w__release(lua_State *L)
 static int w__release(lua_State *L)
@@ -141,8 +181,8 @@ static int w__release(lua_State *L)
 		if (lua_istable(L, -1))
 		if (lua_istable(L, -1))
 		{
 		{
 			// loveobjects[object] = nil
 			// loveobjects[object] = nil
-			lua_Number objectkey = luax_computeloveobjectkey(L, object);
-			lua_pushnumber(L, objectkey);
+			ObjectKey objectkey = luax_computeloveobjectkey(L, object);
+			luax_pushloveobjectkey(L, objectkey);
 			lua_pushnil(L);
 			lua_pushnil(L);
 			lua_settable(L, -3);
 			lua_settable(L, -3);
 		}
 		}
@@ -622,10 +662,10 @@ void luax_pushtype(lua_State *L, love::Type &type, love::Object *object)
 		return luax_rawnewtype(L, type, object);
 		return luax_rawnewtype(L, type, object);
 	}
 	}
 
 
-	lua_Number objectkey = luax_computeloveobjectkey(L, object);
+	ObjectKey objectkey = luax_computeloveobjectkey(L, object);
 
 
 	// Get the value of loveobjects[object] on the stack.
 	// Get the value of loveobjects[object] on the stack.
-	lua_pushnumber(L, objectkey);
+	luax_pushloveobjectkey(L, objectkey);
 	lua_gettable(L, -2);
 	lua_gettable(L, -2);
 
 
 	// If the Proxy userdata isn't in the instantiated types table yet, add it.
 	// If the Proxy userdata isn't in the instantiated types table yet, add it.
@@ -635,7 +675,7 @@ void luax_pushtype(lua_State *L, love::Type &type, love::Object *object)
 
 
 		luax_rawnewtype(L, type, object);
 		luax_rawnewtype(L, type, object);
 
 
-		lua_pushnumber(L, objectkey);
+		luax_pushloveobjectkey(L, objectkey);
 		lua_pushvalue(L, -2);
 		lua_pushvalue(L, -2);
 
 
 		// loveobjects[object] = Proxy.
 		// loveobjects[object] = Proxy.

+ 49 - 19
src/modules/filesystem/physfs/Filesystem.cpp

@@ -118,6 +118,10 @@ Filesystem::Filesystem()
 
 
 Filesystem::~Filesystem()
 Filesystem::~Filesystem()
 {
 {
+#ifdef LOVE_ANDROID
+	love::android::deinitializeVirtualArchive();
+#endif
+
 	if (PHYSFS_isInit())
 	if (PHYSFS_isInit())
 		PHYSFS_deinit();
 		PHYSFS_deinit();
 }
 }
@@ -232,30 +236,56 @@ bool Filesystem::setSource(const char *source)
 	if (!love::android::createStorageDirectories())
 	if (!love::android::createStorageDirectories())
 		SDL_Log("Error creating storage directories!");
 		SDL_Log("Error creating storage directories!");
 
 
-	new_search_path = love::android::getSelectedGameFile();
+	new_search_path = "";
 
 
-	// try mounting first, if that fails, load to memory and mount
-	if (!PHYSFS_mount(new_search_path.c_str(), nullptr, 1))
+	PHYSFS_Io *gameLoveIO;
+	bool hasFusedGame = love::android::checkFusedGame((void **) &gameLoveIO);
+
+	if (hasFusedGame)
 	{
 	{
-		// 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))
+		if (gameLoveIO)
 		{
 		{
-			SDL_Log("Failure memory loading archive %s", new_search_path.c_str());
-			return false;
+			// Actually we should just be able to mount gameLoveIO, but that's experimental.
+			gameLoveIO->destroy(gameLoveIO);
+			goto oldschool;
+		}
+		else
+		{
+			if (!love::android::initializeVirtualArchive())
+			{
+				SDL_Log("Unable to mount AAsset: %s", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
+				return false;
+			}
 		}
 		}
-		if (!PHYSFS_mountMemory(
-			    game_archive_ptr, game_archive_size,
-			    love::android::freeGameArchiveMemory, "archive.zip", "/", 0))
+	}
+	else
+	{
+	oldschool:
+		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))
 		{
 		{
-			SDL_Log("Failure mounting in-memory archive.");
-			love::android::freeGameArchiveMemory(game_archive_ptr);
-			return false;
+			// 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))
+			{
+				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;
+			}
 		}
 		}
 	}
 	}
 #else
 #else

+ 10 - 6
src/modules/graphics/Buffer.cpp

@@ -33,8 +33,8 @@ Buffer::Buffer(Graphics *gfx, const Settings &settings, const std::vector<DataDe
 	: arrayLength(0)
 	: arrayLength(0)
 	, arrayStride(0)
 	, arrayStride(0)
 	, size(size)
 	, size(size)
-	, typeFlags(settings.typeFlags)
-	, usage(settings.usage)
+	, usageFlags(settings.usageFlags)
+	, dataUsage(settings.dataUsage)
 	, mapped(false)
 	, mapped(false)
 {
 {
 	if (size == 0 && arraylength == 0)
 	if (size == 0 && arraylength == 0)
@@ -46,10 +46,11 @@ Buffer::Buffer(Graphics *gfx, const Settings &settings, const std::vector<DataDe
 	const auto &caps = gfx->getCapabilities();
 	const auto &caps = gfx->getCapabilities();
 	bool supportsGLSL3 = caps.features[Graphics::FEATURE_GLSL3];
 	bool supportsGLSL3 = caps.features[Graphics::FEATURE_GLSL3];
 
 
-	bool indexbuffer = settings.typeFlags & TYPEFLAG_INDEX;
-	bool vertexbuffer = settings.typeFlags & TYPEFLAG_VERTEX;
-	bool texelbuffer = settings.typeFlags & TYPEFLAG_TEXEL;
-	bool storagebuffer = settings.typeFlags & TYPEFLAG_SHADER_STORAGE;
+	bool indexbuffer = settings.usageFlags & BUFFERUSAGEFLAG_INDEX;
+	bool vertexbuffer = settings.usageFlags & BUFFERUSAGEFLAG_VERTEX;
+	bool texelbuffer = settings.usageFlags & BUFFERUSAGEFLAG_TEXEL;
+	bool storagebuffer = settings.usageFlags & BUFFERUSAGEFLAG_SHADER_STORAGE;
+	bool copydest = settings.usageFlags & BUFFERUSAGEFLAG_COPY_DEST;
 
 
 	if (!indexbuffer && !vertexbuffer && !texelbuffer && !storagebuffer)
 	if (!indexbuffer && !vertexbuffer && !texelbuffer && !storagebuffer)
 		throw love::Exception("Buffer must be created with at least one buffer type (index, vertex, texel, or shaderstorage).");
 		throw love::Exception("Buffer must be created with at least one buffer type (index, vertex, texel, or shaderstorage).");
@@ -60,6 +61,9 @@ Buffer::Buffer(Graphics *gfx, const Settings &settings, const std::vector<DataDe
 	if (storagebuffer && !caps.features[Graphics::FEATURE_GLSL4])
 	if (storagebuffer && !caps.features[Graphics::FEATURE_GLSL4])
 		throw love::Exception("Shader Storage buffers are not supported on this system (GLSL 4 support is necessary.)");
 		throw love::Exception("Shader Storage buffers are not supported on this system (GLSL 4 support is necessary.)");
 
 
+	if (copydest && dataUsage == BUFFERDATAUSAGE_STREAM)
+		throw love::Exception("Buffers created with 'stream' data usage cannot be used as a copy destination.");
+
 	size_t offset = 0;
 	size_t offset = 0;
 	size_t stride = 0;
 	size_t stride = 0;
 	size_t structurealignment = 1;
 	size_t structurealignment = 1;

+ 15 - 20
src/modules/graphics/Buffer.h

@@ -55,16 +55,6 @@ public:
 		MAP_WRITE_INVALIDATE,
 		MAP_WRITE_INVALIDATE,
 	};
 	};
 
 
-	enum TypeFlags
-	{
-		TYPEFLAG_NONE = 0,
-		TYPEFLAG_VERTEX = 1 << BUFFERTYPE_VERTEX,
-		TYPEFLAG_INDEX = 1 << BUFFERTYPE_INDEX,
-		TYPEFLAG_UNIFORM = 1 << BUFFERTYPE_UNIFORM,
-		TYPEFLAG_TEXEL = 1 << BUFFERTYPE_TEXEL,
-		TYPEFLAG_SHADER_STORAGE = 1 << BUFFERTYPE_SHADER_STORAGE,
-	};
-
 	struct DataDeclaration
 	struct DataDeclaration
 	{
 	{
 		std::string name;
 		std::string name;
@@ -95,13 +85,13 @@ public:
 
 
 	struct Settings
 	struct Settings
 	{
 	{
-		TypeFlags typeFlags;
-		BufferUsage usage;
+		BufferUsageFlags usageFlags;
+		BufferDataUsage dataUsage;
 		bool zeroInitialize;
 		bool zeroInitialize;
 
 
-		Settings(uint32 typeflags, BufferUsage usage)
-			: typeFlags((TypeFlags)typeflags)
-			, usage(usage)
+		Settings(uint32 usageflags, BufferDataUsage dataUsage)
+			: usageFlags((BufferUsageFlags)usageflags)
+			, dataUsage(dataUsage)
 			, zeroInitialize(false)
 			, zeroInitialize(false)
 		{}
 		{}
 	};
 	};
@@ -110,8 +100,8 @@ public:
 	virtual ~Buffer();
 	virtual ~Buffer();
 
 
 	size_t getSize() const { return size; }
 	size_t getSize() const { return size; }
-	TypeFlags getTypeFlags() const { return typeFlags; }
-	BufferUsage getUsage() const { return usage; }
+	BufferUsageFlags getUsageFlags() const { return usageFlags; }
+	BufferDataUsage getDataUsage() const { return dataUsage; }
 	bool isMapped() const { return mapped; }
 	bool isMapped() const { return mapped; }
 
 
 	size_t getArrayLength() const { return arrayLength; }
 	size_t getArrayLength() const { return arrayLength; }
@@ -137,6 +127,11 @@ public:
 	 */
 	 */
 	virtual void fill(size_t offset, size_t size, const void *data) = 0;
 	virtual void fill(size_t offset, size_t size, const void *data) = 0;
 
 
+	/**
+	 * Copy a portion of this Buffer's data to another buffer, using the GPU.
+	 **/
+	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
 	 * Texel buffers may use an additional texture handle as well as a buffer
 	 * handle.
 	 * handle.
@@ -174,11 +169,11 @@ protected:
 	// The size of the buffer, in bytes.
 	// The size of the buffer, in bytes.
 	size_t size;
 	size_t size;
 
 
-	// The type of the buffer object.
-	TypeFlags typeFlags;
+	// Bit flags describing how the buffer can be used.
+	BufferUsageFlags usageFlags;
 
 
 	// Usage hint. GL_[DYNAMIC, STATIC, STREAM]_DRAW.
 	// Usage hint. GL_[DYNAMIC, STATIC, STREAM]_DRAW.
-	BufferUsage usage;
+	BufferDataUsage dataUsage;
 
 
 	bool mapped;
 	bool mapped;
 	
 	

+ 88 - 163
src/modules/graphics/Graphics.cpp

@@ -207,7 +207,7 @@ void Graphics::createQuadIndexBuffer()
 
 
 	size_t size = sizeof(uint16) * (LOVE_UINT16_MAX / 4) * 6;
 	size_t size = sizeof(uint16) * (LOVE_UINT16_MAX / 4) * 6;
 
 
-	Buffer::Settings settings(Buffer::TYPEFLAG_INDEX, BUFFERUSAGE_STATIC);
+	Buffer::Settings settings(BUFFERUSAGEFLAG_INDEX, BUFFERDATAUSAGE_STATIC);
 	quadIndexBuffer = newBuffer(settings, DATAFORMAT_UINT16, nullptr, size, 0);
 	quadIndexBuffer = newBuffer(settings, DATAFORMAT_UINT16, nullptr, size, 0);
 
 
 	Buffer::Mapper map(*quadIndexBuffer);
 	Buffer::Mapper map(*quadIndexBuffer);
@@ -239,7 +239,7 @@ Video *Graphics::newVideo(love::video::VideoStream *stream, float dpiscale)
 	return new Video(this, stream, dpiscale);
 	return new Video(this, stream, dpiscale);
 }
 }
 
 
-love::graphics::SpriteBatch *Graphics::newSpriteBatch(Texture *texture, int size, BufferUsage usage)
+love::graphics::SpriteBatch *Graphics::newSpriteBatch(Texture *texture, int size, BufferDataUsage usage)
 {
 {
 	return new SpriteBatch(this, texture, size, usage);
 	return new SpriteBatch(this, texture, size, usage);
 }
 }
@@ -330,12 +330,12 @@ Buffer *Graphics::newBuffer(const Buffer::Settings &settings, DataFormat format,
 	return newBuffer(settings, dataformat, data, size, arraylength);
 	return newBuffer(settings, dataformat, data, size, arraylength);
 }
 }
 
 
-Mesh *Graphics::newMesh(const std::vector<Buffer::DataDeclaration> &vertexformat, int vertexcount, PrimitiveType drawmode, BufferUsage usage)
+Mesh *Graphics::newMesh(const std::vector<Buffer::DataDeclaration> &vertexformat, int vertexcount, PrimitiveType drawmode, BufferDataUsage usage)
 {
 {
 	return new Mesh(this, vertexformat, vertexcount, drawmode, usage);
 	return new Mesh(this, vertexformat, vertexcount, drawmode, usage);
 }
 }
 
 
-Mesh *Graphics::newMesh(const std::vector<Buffer::DataDeclaration> &vertexformat, const void *data, size_t datasize, PrimitiveType drawmode, BufferUsage usage)
+Mesh *Graphics::newMesh(const std::vector<Buffer::DataDeclaration> &vertexformat, const void *data, size_t datasize, PrimitiveType drawmode, BufferDataUsage usage)
 {
 {
 	return new Mesh(this, vertexformat, data, datasize, drawmode, usage);
 	return new Mesh(this, vertexformat, data, datasize, drawmode, usage);
 }
 }
@@ -1082,6 +1082,32 @@ void Graphics::captureScreenshot(const ScreenshotInfo &info)
 	pendingScreenshotCallbacks.push_back(info);
 	pendingScreenshotCallbacks.push_back(info);
 }
 }
 
 
+void Graphics::copyBuffer(Buffer *source, Buffer *dest, size_t sourceoffset, size_t destoffset, size_t size)
+{
+	if (!capabilities.features[FEATURE_COPY_BUFFER])
+		throw love::Exception("Buffer copying is not supported on this system.");
+
+	if (!(source->getUsageFlags() & BUFFERUSAGEFLAG_COPY_SOURCE))
+		throw love::Exception("Copy source buffer must be created with the copysource flag.");
+
+	if (!(dest->getUsageFlags() & BUFFERUSAGEFLAG_COPY_DEST))
+		throw love::Exception("Copy destination buffer must be created with the copydest flag.");
+
+	Range sourcerange(sourceoffset, size);
+	Range destrange(destoffset, size);
+
+	if (sourcerange.getMax() >= source->getSize())
+		throw love::Exception("Buffer copy source offset and size doesn't fit within the source Buffer's size.");
+
+	if (destrange.getMax() >= dest->getSize())
+		throw love::Exception("Buffer copy destination offset and size doesn't fit within the destination buffer's size.");
+
+	if (source == dest && sourcerange.intersects(destrange))
+		throw love::Exception("Copying a portion of a buffer to the same buffer requires non-overlapping source and destination offsets.");
+
+	source->copyTo(dest, sourceoffset, destoffset, size);
+}
+
 Graphics::BatchedVertexData Graphics::requestBatchedDraw(const BatchedDrawCommand &cmd)
 Graphics::BatchedVertexData Graphics::requestBatchedDraw(const BatchedDrawCommand &cmd)
 {
 {
 	BatchedDrawState &state = batchedDrawState;
 	BatchedDrawState &state = batchedDrawState;
@@ -1155,11 +1181,14 @@ Graphics::BatchedVertexData Graphics::requestBatchedDraw(const BatchedDrawComman
 		state.standardShaderType = cmd.standardShaderType;
 		state.standardShaderType = cmd.standardShaderType;
 	}
 	}
 
 
-	if (state.vertexCount == 0 && Shader::isDefaultActive())
-		Shader::attachDefault(state.standardShaderType);
+	if (state.vertexCount == 0)
+	{
+		if (Shader::isDefaultActive())
+			Shader::attachDefault(state.standardShaderType);
 
 
-	if (state.vertexCount == 0 && Shader::current != nullptr && cmd.texture != nullptr)
-		Shader::current->checkMainTexture(cmd.texture);
+		if (Shader::current != nullptr)
+			Shader::current->validateDrawState(cmd.primitiveMode, cmd.texture);
+	}
 
 
 	if (shouldresize)
 	if (shouldresize)
 	{
 	{
@@ -1168,14 +1197,14 @@ Graphics::BatchedVertexData Graphics::requestBatchedDraw(const BatchedDrawComman
 			if (state.vb[i]->getSize() < buffersizes[i])
 			if (state.vb[i]->getSize() < buffersizes[i])
 			{
 			{
 				delete state.vb[i];
 				delete state.vb[i];
-				state.vb[i] = newStreamBuffer(BUFFERTYPE_VERTEX, buffersizes[i]);
+				state.vb[i] = newStreamBuffer(BUFFERUSAGE_VERTEX, buffersizes[i]);
 			}
 			}
 		}
 		}
 
 
 		if (state.indexBuffer->getSize() < buffersizes[2])
 		if (state.indexBuffer->getSize() < buffersizes[2])
 		{
 		{
 			delete state.indexBuffer;
 			delete state.indexBuffer;
-			state.indexBuffer = newStreamBuffer(BUFFERTYPE_INDEX, buffersizes[2]);
+			state.indexBuffer = newStreamBuffer(BUFFERUSAGE_INDEX, buffersizes[2]);
 		}
 		}
 	}
 	}
 
 
@@ -1367,6 +1396,7 @@ void Graphics::points(const Vector2 *positions, const Colorf *colors, size_t num
 	cmd.formats[0] = getSinglePositionFormat(is2D);
 	cmd.formats[0] = getSinglePositionFormat(is2D);
 	cmd.formats[1] = CommonFormat::RGBAub;
 	cmd.formats[1] = CommonFormat::RGBAub;
 	cmd.vertexCount = (int) numpoints;
 	cmd.vertexCount = (int) numpoints;
+	cmd.standardShaderType = Shader::STANDARD_POINTS;
 
 
 	BatchedVertexData data = requestBatchedDraw(cmd);
 	BatchedVertexData data = requestBatchedDraw(cmd);
 
 
@@ -1835,179 +1865,74 @@ Vector2 Graphics::inverseTransformPoint(Vector2 point)
 	return p;
 	return p;
 }
 }
 
 
-/**
- * Constants.
- **/
-
-bool Graphics::getConstant(const char *in, DrawMode &out)
-{
-	return drawModes.find(in, out);
-}
-
-bool Graphics::getConstant(DrawMode in, const char *&out)
-{
-	return drawModes.find(in, out);
-}
-
-std::vector<std::string> Graphics::getConstants(DrawMode)
-{
-	return drawModes.getNames();
-}
-
-bool Graphics::getConstant(const char *in, ArcMode &out)
-{
-	return arcModes.find(in, out);
-}
-
-bool Graphics::getConstant(ArcMode in, const char *&out)
-{
-	return arcModes.find(in, out);
-}
-
-std::vector<std::string> Graphics::getConstants(ArcMode)
-{
-	return arcModes.getNames();
-}
-
-bool Graphics::getConstant(const char *in, LineStyle &out)
-{
-	return lineStyles.find(in, out);
-}
-
-bool Graphics::getConstant(LineStyle in, const char *&out)
+STRINGMAP_CLASS_BEGIN(Graphics, Graphics::DrawMode, Graphics::DRAW_MAX_ENUM, drawMode)
 {
 {
-	return lineStyles.find(in, out);
+	{ "line", Graphics::DRAW_LINE },
+	{ "fill", Graphics::DRAW_FILL },
 }
 }
+STRINGMAP_CLASS_END(Graphics, Graphics::DrawMode, Graphics::DRAW_MAX_ENUM, drawMode)
 
 
-std::vector<std::string> Graphics::getConstants(LineStyle)
+STRINGMAP_CLASS_BEGIN(Graphics, Graphics::ArcMode, Graphics::ARC_MAX_ENUM, arcMode)
 {
 {
-	return lineStyles.getNames();
+	{ "open",   Graphics::ARC_OPEN   },
+	{ "closed", Graphics::ARC_CLOSED },
+	{ "pie",    Graphics::ARC_PIE    },
 }
 }
+STRINGMAP_CLASS_END(Graphics, Graphics::ArcMode, Graphics::ARC_MAX_ENUM, arcMode)
 
 
-bool Graphics::getConstant(const char *in, LineJoin &out)
+STRINGMAP_CLASS_BEGIN(Graphics, Graphics::LineStyle, Graphics::LINE_MAX_ENUM, lineStyle)
 {
 {
-	return lineJoins.find(in, out);
+	{ "smooth", Graphics::LINE_SMOOTH },
+	{ "rough",  Graphics::LINE_ROUGH  }
 }
 }
+STRINGMAP_CLASS_END(Graphics, Graphics::LineStyle, Graphics::LINE_MAX_ENUM, lineStyle)
 
 
-bool Graphics::getConstant(LineJoin in, const char *&out)
+STRINGMAP_CLASS_BEGIN(Graphics, Graphics::LineJoin, Graphics::LINE_JOIN_MAX_ENUM, lineJoin)
 {
 {
-	return lineJoins.find(in, out);
+	{ "none",  Graphics::LINE_JOIN_NONE  },
+	{ "miter", Graphics::LINE_JOIN_MITER },
+	{ "bevel", Graphics::LINE_JOIN_BEVEL }
 }
 }
+STRINGMAP_CLASS_END(Graphics, Graphics::LineJoin, Graphics::LINE_JOIN_MAX_ENUM, lineJoin)
 
 
-std::vector<std::string> Graphics::getConstants(LineJoin)
+STRINGMAP_CLASS_BEGIN(Graphics, Graphics::Feature, Graphics::FEATURE_MAX_ENUM, feature)
 {
 {
-	return lineJoins.getNames();
+	{ "multirendertargetformats", Graphics::FEATURE_MULTI_RENDER_TARGET_FORMATS },
+	{ "clampzero",                Graphics::FEATURE_CLAMP_ZERO           },
+	{ "blendminmax",              Graphics::FEATURE_BLEND_MINMAX         },
+	{ "lighten",                  Graphics::FEATURE_LIGHTEN              },
+	{ "fullnpot",                 Graphics::FEATURE_FULL_NPOT            },
+	{ "pixelshaderhighp",         Graphics::FEATURE_PIXEL_SHADER_HIGHP   },
+	{ "shaderderivatives",        Graphics::FEATURE_SHADER_DERIVATIVES   },
+	{ "glsl3",                    Graphics::FEATURE_GLSL3                },
+	{ "glsl4",                    Graphics::FEATURE_GLSL4                },
+	{ "instancing",               Graphics::FEATURE_INSTANCING           },
+	{ "texelbuffer",              Graphics::FEATURE_TEXEL_BUFFER         },
+	{ "copybuffer",               Graphics::FEATURE_COPY_BUFFER          },
 }
 }
+STRINGMAP_CLASS_END(Graphics, Graphics::Feature, Graphics::FEATURE_MAX_ENUM, feature)
 
 
-bool Graphics::getConstant(const char *in, Feature &out)
+STRINGMAP_CLASS_BEGIN(Graphics, Graphics::SystemLimit, Graphics::LIMIT_MAX_ENUM, systemLimit)
 {
 {
-	return features.find(in, out);
+	{ "pointsize",               Graphics::LIMIT_POINT_SIZE                 },
+	{ "texturesize",             Graphics::LIMIT_TEXTURE_SIZE               },
+	{ "texturelayers",           Graphics::LIMIT_TEXTURE_LAYERS             },
+	{ "volumetexturesize",       Graphics::LIMIT_VOLUME_TEXTURE_SIZE        },
+	{ "cubetexturesize",         Graphics::LIMIT_CUBE_TEXTURE_SIZE          },
+	{ "texelbuffersize",         Graphics::LIMIT_TEXEL_BUFFER_SIZE          },
+	{ "shaderstoragebuffersize", Graphics::LIMIT_SHADER_STORAGE_BUFFER_SIZE },
+	{ "rendertargets",           Graphics::LIMIT_RENDER_TARGETS             },
+	{ "texturemsaa",             Graphics::LIMIT_TEXTURE_MSAA               },
+	{ "anisotropy",              Graphics::LIMIT_ANISOTROPY                 },
 }
 }
+STRINGMAP_CLASS_END(Graphics, Graphics::SystemLimit, Graphics::LIMIT_MAX_ENUM, systemLimit)
 
 
-bool Graphics::getConstant(Feature in, const char *&out)
+STRINGMAP_CLASS_BEGIN(Graphics, Graphics::StackType, Graphics::STACK_MAX_ENUM, stackType)
 {
 {
-	return features.find(in, out);
+	{ "all",       Graphics::STACK_ALL       },
+	{ "transform", Graphics::STACK_TRANSFORM },
 }
 }
-
-bool Graphics::getConstant(const char *in, SystemLimit &out)
-{
-	return systemLimits.find(in, out);
-}
-
-bool Graphics::getConstant(SystemLimit in, const char *&out)
-{
-	return systemLimits.find(in, out);
-}
-
-bool Graphics::getConstant(const char *in, StackType &out)
-{
-	return stackTypes.find(in, out);
-}
-
-bool Graphics::getConstant(StackType in, const char *&out)
-{
-	return stackTypes.find(in, out);
-}
-
-std::vector<std::string> Graphics::getConstants(StackType)
-{
-	return stackTypes.getNames();
-}
-
-StringMap<Graphics::DrawMode, Graphics::DRAW_MAX_ENUM>::Entry Graphics::drawModeEntries[] =
-{
-	{ "line", DRAW_LINE },
-	{ "fill", DRAW_FILL },
-};
-
-StringMap<Graphics::DrawMode, Graphics::DRAW_MAX_ENUM> Graphics::drawModes(Graphics::drawModeEntries, sizeof(Graphics::drawModeEntries));
-
-StringMap<Graphics::ArcMode, Graphics::ARC_MAX_ENUM>::Entry Graphics::arcModeEntries[] =
-{
-	{ "open",   ARC_OPEN   },
-	{ "closed", ARC_CLOSED },
-	{ "pie",    ARC_PIE    },
-};
-
-StringMap<Graphics::ArcMode, Graphics::ARC_MAX_ENUM> Graphics::arcModes(Graphics::arcModeEntries, sizeof(Graphics::arcModeEntries));
-
-StringMap<Graphics::LineStyle, Graphics::LINE_MAX_ENUM>::Entry Graphics::lineStyleEntries[] =
-{
-	{ "smooth", LINE_SMOOTH },
-	{ "rough",  LINE_ROUGH  }
-};
-
-StringMap<Graphics::LineStyle, Graphics::LINE_MAX_ENUM> Graphics::lineStyles(Graphics::lineStyleEntries, sizeof(Graphics::lineStyleEntries));
-
-StringMap<Graphics::LineJoin, Graphics::LINE_JOIN_MAX_ENUM>::Entry Graphics::lineJoinEntries[] =
-{
-	{ "none",  LINE_JOIN_NONE  },
-	{ "miter", LINE_JOIN_MITER },
-	{ "bevel", LINE_JOIN_BEVEL }
-};
-
-StringMap<Graphics::LineJoin, Graphics::LINE_JOIN_MAX_ENUM> Graphics::lineJoins(Graphics::lineJoinEntries, sizeof(Graphics::lineJoinEntries));
-
-StringMap<Graphics::Feature, Graphics::FEATURE_MAX_ENUM>::Entry Graphics::featureEntries[] =
-{
-	{ "multirendertargetformats", FEATURE_MULTI_RENDER_TARGET_FORMATS },
-	{ "clampzero",                FEATURE_CLAMP_ZERO           },
-	{ "blendminmax",              FEATURE_BLEND_MINMAX         },
-	{ "lighten",                  FEATURE_LIGHTEN              },
-	{ "fullnpot",                 FEATURE_FULL_NPOT            },
-	{ "pixelshaderhighp",         FEATURE_PIXEL_SHADER_HIGHP   },
-	{ "shaderderivatives",        FEATURE_SHADER_DERIVATIVES   },
-	{ "glsl3",                    FEATURE_GLSL3                },
-	{ "glsl4",                    FEATURE_GLSL4                },
-	{ "instancing",               FEATURE_INSTANCING           },
-	{ "texelbuffer",              FEATURE_TEXEL_BUFFER         },
-};
-
-StringMap<Graphics::Feature, Graphics::FEATURE_MAX_ENUM> Graphics::features(Graphics::featureEntries, sizeof(Graphics::featureEntries));
-
-StringMap<Graphics::SystemLimit, Graphics::LIMIT_MAX_ENUM>::Entry Graphics::systemLimitEntries[] =
-{
-	{ "pointsize",               LIMIT_POINT_SIZE                 },
-	{ "texturesize",             LIMIT_TEXTURE_SIZE               },
-	{ "texturelayers",           LIMIT_TEXTURE_LAYERS             },
-	{ "volumetexturesize",       LIMIT_VOLUME_TEXTURE_SIZE        },
-	{ "cubetexturesize",         LIMIT_CUBE_TEXTURE_SIZE          },
-	{ "texelbuffersize",         LIMIT_TEXEL_BUFFER_SIZE          },
-	{ "shaderstoragebuffersize", LIMIT_SHADER_STORAGE_BUFFER_SIZE },
-	{ "rendertargets",           LIMIT_RENDER_TARGETS             },
-	{ "texturemsaa",             LIMIT_TEXTURE_MSAA               },
-	{ "anisotropy",              LIMIT_ANISOTROPY                 },
-};
-
-StringMap<Graphics::SystemLimit, Graphics::LIMIT_MAX_ENUM> Graphics::systemLimits(Graphics::systemLimitEntries, sizeof(Graphics::systemLimitEntries));
-
-StringMap<Graphics::StackType, Graphics::STACK_MAX_ENUM>::Entry Graphics::stackTypeEntries[] =
-{
-	{ "all",       STACK_ALL       },
-	{ "transform", STACK_TRANSFORM },
-};
-
-StringMap<Graphics::StackType, Graphics::STACK_MAX_ENUM> Graphics::stackTypes(Graphics::stackTypeEntries, sizeof(Graphics::stackTypeEntries));
+STRINGMAP_CLASS_END(Graphics, Graphics::StackType, Graphics::STACK_MAX_ENUM, stackType)
 
 
 } // graphics
 } // graphics
 } // love
 } // love

+ 15 - 51
src/modules/graphics/Graphics.h

@@ -144,6 +144,7 @@ public:
 		FEATURE_GLSL4,
 		FEATURE_GLSL4,
 		FEATURE_INSTANCING,
 		FEATURE_INSTANCING,
 		FEATURE_TEXEL_BUFFER,
 		FEATURE_TEXEL_BUFFER,
+		FEATURE_COPY_BUFFER,
 		FEATURE_MAX_ENUM
 		FEATURE_MAX_ENUM
 	};
 	};
 
 
@@ -432,7 +433,7 @@ public:
 	Font *newDefaultFont(int size, font::TrueTypeRasterizer::Hinting hinting);
 	Font *newDefaultFont(int size, font::TrueTypeRasterizer::Hinting hinting);
 	Video *newVideo(love::video::VideoStream *stream, float dpiscale);
 	Video *newVideo(love::video::VideoStream *stream, float dpiscale);
 
 
-	SpriteBatch *newSpriteBatch(Texture *texture, int size, BufferUsage usage);
+	SpriteBatch *newSpriteBatch(Texture *texture, int size, BufferDataUsage usage);
 	ParticleSystem *newParticleSystem(Texture *texture, int size);
 	ParticleSystem *newParticleSystem(Texture *texture, int size);
 
 
 	Shader *newShader(const std::vector<std::string> &stagessource);
 	Shader *newShader(const std::vector<std::string> &stagessource);
@@ -440,8 +441,8 @@ public:
 	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, 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(const Buffer::Settings &settings, DataFormat format, const void *data, size_t size, size_t arraylength);
 
 
-	Mesh *newMesh(const std::vector<Buffer::DataDeclaration> &vertexformat, int vertexcount, PrimitiveType drawmode, BufferUsage usage);
-	Mesh *newMesh(const std::vector<Buffer::DataDeclaration> &vertexformat, const void *data, size_t datasize, PrimitiveType drawmode, BufferUsage usage);
+	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<Mesh::BufferAttribute> &attributes, PrimitiveType drawmode);
 
 
 	Text *newText(Font *font, const std::vector<Font::ColoredString> &text = {});
 	Text *newText(Font *font, const std::vector<Font::ColoredString> &text = {});
@@ -669,6 +670,8 @@ public:
 
 
 	void captureScreenshot(const ScreenshotInfo &info);
 	void captureScreenshot(const ScreenshotInfo &info);
 
 
+	void copyBuffer(Buffer *source, Buffer *dest, size_t sourceoffset, size_t destoffset, size_t size);
+
 	void draw(Drawable *drawable, const Matrix4 &m);
 	void draw(Drawable *drawable, const Matrix4 &m);
 	void draw(Texture *texture, Quad *quad, 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, const Matrix4 &m);
@@ -776,7 +779,7 @@ public:
 	/**
 	/**
 	 * Converts PIXELFORMAT_NORMAL and PIXELFORMAT_HDR into a real format.
 	 * Converts PIXELFORMAT_NORMAL and PIXELFORMAT_HDR into a real format.
 	 **/
 	 **/
-	virtual PixelFormat getSizedFormat(PixelFormat format, bool rendertarget, bool readable, bool sRGB) const = 0;
+	virtual PixelFormat getSizedFormat(PixelFormat format, bool rendertarget, bool readable) const = 0;
 
 
 	/**
 	/**
 	 * Gets whether the specified pixel format is supported.
 	 * Gets whether the specified pixel format is supported.
@@ -848,31 +851,13 @@ public:
 
 
 	static Graphics *createInstance(const std::vector<Renderer> &renderers);
 	static Graphics *createInstance(const std::vector<Renderer> &renderers);
 
 
-	static bool getConstant(const char *in, DrawMode &out);
-	static bool getConstant(DrawMode in, const char *&out);
-	static std::vector<std::string> getConstants(DrawMode);
-
-	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, 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);
+	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:
 protected:
 
 
@@ -952,7 +937,7 @@ protected:
 	ShaderStage *newShaderStage(ShaderStage::StageType stage, const std::string &source, const Shader::SourceInfo &info);
 	ShaderStage *newShaderStage(ShaderStage::StageType stage, const std::string &source, const Shader::SourceInfo &info);
 	virtual ShaderStage *newShaderStageInternal(ShaderStage::StageType stage, const std::string &cachekey, const std::string &source, bool gles) = 0;
 	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 Shader *newShaderInternal(ShaderStage *vertex, ShaderStage *pixel) = 0;
-	virtual StreamBuffer *newStreamBuffer(BufferType type, size_t size) = 0;
+	virtual StreamBuffer *newStreamBuffer(BufferUsage type, size_t size) = 0;
 
 
 	virtual void setRenderTargetsInternal(const RenderTargets &rts, int w, int h, int pixelw, int pixelh, bool hasSRGBtexture) = 0;
 	virtual void setRenderTargetsInternal(const RenderTargets &rts, int w, int h, int pixelw, int pixelh, bool hasSRGBtexture) = 0;
 
 
@@ -1016,27 +1001,6 @@ private:
 
 
 	std::unordered_map<std::string, ShaderStage *> cachedShaderStages[ShaderStage::STAGE_MAX_ENUM];
 	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<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;
-
 }; // Graphics
 }; // Graphics
 
 
 } // graphics
 } // graphics

+ 14 - 14
src/modules/graphics/Mesh.cpp

@@ -45,7 +45,7 @@ std::vector<Buffer::DataDeclaration> Mesh::getDefaultVertexFormat()
 
 
 love::Type Mesh::type("Mesh", &Drawable::type);
 love::Type Mesh::type("Mesh", &Drawable::type);
 
 
-Mesh::Mesh(graphics::Graphics *gfx, const std::vector<Buffer::DataDeclaration> &vertexformat, const void *data, size_t datasize, PrimitiveType drawmode, BufferUsage usage)
+Mesh::Mesh(graphics::Graphics *gfx, const std::vector<Buffer::DataDeclaration> &vertexformat, const void *data, size_t datasize, PrimitiveType drawmode, BufferDataUsage usage)
 	: primitiveType(drawmode)
 	: primitiveType(drawmode)
 {
 {
 	try
 	try
@@ -59,7 +59,7 @@ Mesh::Mesh(graphics::Graphics *gfx, const std::vector<Buffer::DataDeclaration> &
 
 
 	memcpy(vertexData, data, datasize);
 	memcpy(vertexData, data, datasize);
 
 
-	Buffer::Settings settings(Buffer::TYPEFLAG_VERTEX, usage);
+	Buffer::Settings settings(BUFFERUSAGEFLAG_VERTEX, usage);
 	vertexBuffer.set(gfx->newBuffer(settings, vertexformat, vertexData, datasize, 0), Acquire::NORETAIN);
 	vertexBuffer.set(gfx->newBuffer(settings, vertexformat, vertexData, datasize, 0), Acquire::NORETAIN);
 
 
 	vertexCount = vertexBuffer->getArrayLength();
 	vertexCount = vertexBuffer->getArrayLength();
@@ -71,7 +71,7 @@ Mesh::Mesh(graphics::Graphics *gfx, const std::vector<Buffer::DataDeclaration> &
 	indexDataType = getIndexDataTypeFromMax(vertexCount);
 	indexDataType = getIndexDataTypeFromMax(vertexCount);
 }
 }
 
 
-Mesh::Mesh(graphics::Graphics *gfx, const std::vector<Buffer::DataDeclaration> &vertexformat, int vertexcount, PrimitiveType drawmode, BufferUsage usage)
+Mesh::Mesh(graphics::Graphics *gfx, const std::vector<Buffer::DataDeclaration> &vertexformat, int vertexcount, PrimitiveType drawmode, BufferDataUsage usage)
 	: vertexCount((size_t) vertexcount)
 	: vertexCount((size_t) vertexcount)
 	, indexDataType(getIndexDataTypeFromMax(vertexcount))
 	, indexDataType(getIndexDataTypeFromMax(vertexcount))
 	, primitiveType(drawmode)
 	, primitiveType(drawmode)
@@ -79,7 +79,7 @@ Mesh::Mesh(graphics::Graphics *gfx, const std::vector<Buffer::DataDeclaration> &
 	if (vertexcount <= 0)
 	if (vertexcount <= 0)
 		throw love::Exception("Invalid number of vertices (%d).", vertexcount);
 		throw love::Exception("Invalid number of vertices (%d).", vertexcount);
 
 
-	Buffer::Settings settings(Buffer::TYPEFLAG_VERTEX, usage);
+	Buffer::Settings settings(BUFFERUSAGEFLAG_VERTEX, usage);
 	vertexBuffer.set(gfx->newBuffer(settings, vertexformat, nullptr, 0, vertexcount), Acquire::NORETAIN);
 	vertexBuffer.set(gfx->newBuffer(settings, vertexformat, nullptr, 0, vertexcount), Acquire::NORETAIN);
 
 
 	vertexStride = vertexBuffer->getArrayStride();
 	vertexStride = vertexBuffer->getArrayStride();
@@ -112,7 +112,7 @@ Mesh::Mesh(const std::vector<Mesh::BufferAttribute> &attributes, PrimitiveType d
 
 
 	for (const auto &attrib : attachedAttributes)
 	for (const auto &attrib : attachedAttributes)
 	{
 	{
-		if ((attrib.buffer->getTypeFlags() & Buffer::TYPEFLAG_VERTEX) == 0)
+		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.");
 			throw love::Exception("Buffer must be created with vertex buffer support to be used as a Mesh vertex attribute.");
 
 
 		if (getAttachedAttributeIndex(attrib.name) != -1)
 		if (getAttachedAttributeIndex(attrib.name) != -1)
@@ -210,7 +210,7 @@ bool Mesh::isAttributeEnabled(const std::string &name) const
 
 
 void Mesh::attachAttribute(const std::string &name, Buffer *buffer, Mesh *mesh, const std::string &attachname, AttributeStep step)
 void Mesh::attachAttribute(const std::string &name, Buffer *buffer, Mesh *mesh, const std::string &attachname, AttributeStep step)
 {
 {
-	if ((buffer->getTypeFlags() & Buffer::TYPEFLAG_VERTEX) == 0)
+	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.");
 		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);
 	auto gfx = Module::getInstance<Graphics>(Module::M_GRAPHICS);
@@ -276,7 +276,7 @@ void Mesh::flush()
 {
 {
 	if (vertexBuffer.get() && vertexData != nullptr && modifiedVertexData.isValid())
 	if (vertexBuffer.get() && vertexData != nullptr && modifiedVertexData.isValid())
 	{
 	{
-		if (vertexBuffer->getUsage() == BUFFERUSAGE_STREAM)
+		if (vertexBuffer->getDataUsage() == BUFFERDATAUSAGE_STREAM)
 		{
 		{
 			vertexBuffer->fill(0, vertexBuffer->getSize(), vertexData);
 			vertexBuffer->fill(0, vertexBuffer->getSize(), vertexData);
 		}
 		}
@@ -327,8 +327,8 @@ void Mesh::setVertexMap(const std::vector<uint32> &map)
 	if (indexBuffer.get() == nullptr || size > indexBuffer->getSize() || indexBuffer->getDataMember(0).decl.format != dataformat)
 	if (indexBuffer.get() == nullptr || size > indexBuffer->getSize() || indexBuffer->getDataMember(0).decl.format != dataformat)
 	{
 	{
 		auto gfx = Module::getInstance<graphics::Graphics>(Module::M_GRAPHICS);
 		auto gfx = Module::getInstance<graphics::Graphics>(Module::M_GRAPHICS);
-		auto usage = vertexBuffer.get() ? vertexBuffer->getUsage() : BUFFERUSAGE_DYNAMIC;
-		Buffer::Settings settings(Buffer::TYPEFLAG_INDEX, usage);
+		auto usage = vertexBuffer.get() ? vertexBuffer->getDataUsage() : BUFFERDATAUSAGE_DYNAMIC;
+		Buffer::Settings settings(BUFFERUSAGEFLAG_INDEX, usage);
 		indexBuffer.set(gfx->newBuffer(settings, dataformat, nullptr, size, 0), Acquire::NORETAIN);
 		indexBuffer.set(gfx->newBuffer(settings, dataformat, nullptr, size, 0), Acquire::NORETAIN);
 	}
 	}
 
 
@@ -360,8 +360,8 @@ void Mesh::setVertexMap(IndexDataType datatype, const void *data, size_t datasiz
 	if (indexBuffer.get() == nullptr || datasize > indexBuffer->getSize() || indexBuffer->getDataMember(0).decl.format != dataformat)
 	if (indexBuffer.get() == nullptr || datasize > indexBuffer->getSize() || indexBuffer->getDataMember(0).decl.format != dataformat)
 	{
 	{
 		auto gfx = Module::getInstance<graphics::Graphics>(Module::M_GRAPHICS);
 		auto gfx = Module::getInstance<graphics::Graphics>(Module::M_GRAPHICS);
-		auto usage = vertexBuffer.get() ? vertexBuffer->getUsage() : BUFFERUSAGE_DYNAMIC;
-		Buffer::Settings settings(Buffer::TYPEFLAG_INDEX, usage);
+		auto usage = vertexBuffer.get() ? vertexBuffer->getDataUsage() : BUFFERDATAUSAGE_DYNAMIC;
+		Buffer::Settings settings(BUFFERUSAGEFLAG_INDEX, usage);
 		indexBuffer.set(gfx->newBuffer(settings, dataformat, nullptr, datasize, 0), Acquire::NORETAIN);
 		indexBuffer.set(gfx->newBuffer(settings, dataformat, nullptr, datasize, 0), Acquire::NORETAIN);
 	}
 	}
 
 
@@ -423,7 +423,7 @@ void Mesh::setIndexBuffer(Buffer *buffer)
 {
 {
 	// Buffer constructor does the rest of the validation for index buffers
 	// Buffer constructor does the rest of the validation for index buffers
 	// (data member formats, etc.)
 	// (data member formats, etc.)
-	if (buffer != nullptr && (buffer->getTypeFlags() & Buffer::TYPEFLAG_INDEX) == 0)
+	if (buffer != nullptr && (buffer->getUsageFlags() & BUFFERUSAGEFLAG_INDEX) == 0)
 		throw love::Exception("setIndexBuffer requires a Buffer created as an index buffer.");
 		throw love::Exception("setIndexBuffer requires a Buffer created as an index buffer.");
 
 
 	indexBuffer.set(buffer);
 	indexBuffer.set(buffer);
@@ -519,8 +519,8 @@ void Mesh::drawInstanced(Graphics *gfx, const Matrix4 &m, int instancecount)
 	if (Shader::isDefaultActive())
 	if (Shader::isDefaultActive())
 		Shader::attachDefault(Shader::STANDARD_DEFAULT);
 		Shader::attachDefault(Shader::STANDARD_DEFAULT);
 
 
-	if (Shader::current && texture.get())
-		Shader::current->checkMainTexture(texture);
+	if (Shader::current)
+		Shader::current->validateDrawState(primitiveType, texture);
 
 
 	VertexAttributes attributes;
 	VertexAttributes attributes;
 	BufferBindings buffers;
 	BufferBindings buffers;

+ 2 - 2
src/modules/graphics/Mesh.h

@@ -63,8 +63,8 @@ public:
 
 
 	static love::Type type;
 	static love::Type type;
 
 
-	Mesh(Graphics *gfx, const std::vector<Buffer::DataDeclaration> &vertexformat, const void *data, size_t datasize, PrimitiveType drawmode, BufferUsage usage);
-	Mesh(Graphics *gfx, const std::vector<Buffer::DataDeclaration> &vertexformat, int vertexcount, PrimitiveType drawmode, BufferUsage usage);
+	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);
 	Mesh(const std::vector<BufferAttribute> &attributes, PrimitiveType drawmode);
 
 
 	virtual ~Mesh();
 	virtual ~Mesh();

+ 3 - 3
src/modules/graphics/ParticleSystem.cpp

@@ -191,7 +191,7 @@ void ParticleSystem::createBuffers(size_t size)
 		auto gfx = Module::getInstance<Graphics>(Module::M_GRAPHICS);
 		auto gfx = Module::getInstance<Graphics>(Module::M_GRAPHICS);
 
 
 		size_t bytes = sizeof(Vertex) * size * 4;
 		size_t bytes = sizeof(Vertex) * size * 4;
-		Buffer::Settings settings(Buffer::TYPEFLAG_VERTEX, BUFFERUSAGE_STREAM);
+		Buffer::Settings settings(BUFFERUSAGEFLAG_VERTEX, BUFFERDATAUSAGE_STREAM);
 		auto decl = Buffer::getCommonFormatDeclaration(CommonFormat::XYf_STf_RGBAub);
 		auto decl = Buffer::getCommonFormatDeclaration(CommonFormat::XYf_STf_RGBAub);
 		buffer = gfx->newBuffer(settings, decl, nullptr, bytes, 0);
 		buffer = gfx->newBuffer(settings, decl, nullptr, bytes, 0);
 	}
 	}
@@ -1037,8 +1037,8 @@ void ParticleSystem::draw(Graphics *gfx, const Matrix4 &m)
 	if (Shader::isDefaultActive())
 	if (Shader::isDefaultActive())
 		Shader::attachDefault(Shader::STANDARD_DEFAULT);
 		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 *positions = texture->getQuad()->getVertexPositions();
 	const Vector2 *texcoords = texture->getQuad()->getVertexTexCoords();
 	const Vector2 *texcoords = texture->getQuad()->getVertexTexCoords();

+ 49 - 25
src/modules/graphics/Shader.cpp

@@ -28,6 +28,7 @@
 
 
 // Needed for reflection information.
 // Needed for reflection information.
 #include "libraries/glslang/glslang/Include/Types.h"
 #include "libraries/glslang/glslang/Include/Types.h"
+#include "libraries/glslang/glslang/MachineIndependent/localintermediate.h"
 
 
 // C++
 // C++
 #include <string>
 #include <string>
@@ -99,6 +100,10 @@ LOVE_HIGHP_OR_MEDIUMP mat3 NormalMatrix;
 LOVE_HIGHP_OR_MEDIUMP vec4 love_ScreenSize;
 LOVE_HIGHP_OR_MEDIUMP vec4 love_ScreenSize;
 LOVE_HIGHP_OR_MEDIUMP vec4 ConstantColor;
 LOVE_HIGHP_OR_MEDIUMP vec4 ConstantColor;
 
 
+LOVE_HIGHP_OR_MEDIUMP float CurrentDPIScale;
+
+LOVE_HIGHP_OR_MEDIUMP float ConstantPointSize;
+
 #define TransformProjectionMatrix (ProjectionMatrix * TransformMatrix)
 #define TransformProjectionMatrix (ProjectionMatrix * TransformMatrix)
 
 
 // Alternate names
 // Alternate names
@@ -128,6 +133,8 @@ void love_initializeBuiltinUniforms() {
 	   love_UniformsPerDraw[10].xyz
 	   love_UniformsPerDraw[10].xyz
 	);
 	);
 
 
+	CurrentDPIScale = love_UniformsPerDraw[8].w;
+	ConstantPointSize = love_UniformsPerDraw[9].w;
 	love_ScreenSize = love_UniformsPerDraw[11];
 	love_ScreenSize = love_UniformsPerDraw[11];
 	ConstantColor = love_UniformsPerDraw[12];
 	ConstantColor = love_UniformsPerDraw[12];
 }
 }
@@ -242,6 +249,7 @@ mediump vec4 linearToGammaFast(mediump vec4 c) { return vec4(linearToGammaFast(c
 
 
 static const char vertex_header[] = R"(
 static const char vertex_header[] = R"(
 #define love_Position gl_Position
 #define love_Position gl_Position
+#define love_PointSize gl_PointSize
 
 
 #if __VERSION__ >= 130
 #if __VERSION__ >= 130
 	#define attribute in
 	#define attribute in
@@ -251,19 +259,9 @@ static const char vertex_header[] = R"(
 		#define love_InstanceID gl_InstanceID
 		#define love_InstanceID gl_InstanceID
 	#endif
 	#endif
 #endif
 #endif
-
-#ifdef GL_ES
-	uniform mediump float love_PointSize;
-#endif
 )";
 )";
 
 
-static const char vertex_functions[] = R"(
-void setPointSize() {
-#ifdef GL_ES
-	gl_PointSize = love_PointSize;
-#endif
-}
-)";
+static const char vertex_functions[] = R"()";
 
 
 static const char vertex_main[] = R"(
 static const char vertex_main[] = R"(
 attribute vec4 VertexPosition;
 attribute vec4 VertexPosition;
@@ -279,7 +277,6 @@ void main() {
 	love_initializeBuiltinUniforms();
 	love_initializeBuiltinUniforms();
 	VaryingTexCoord = VertexTexCoord;
 	VaryingTexCoord = VertexTexCoord;
 	VaryingColor = gammaCorrectColor(VertexColor) * ConstantColor;
 	VaryingColor = gammaCorrectColor(VertexColor) * ConstantColor;
-	setPointSize();
 	love_Position = position(ClipSpaceFromLocal, VertexPosition);
 	love_Position = position(ClipSpaceFromLocal, VertexPosition);
 }
 }
 )";
 )";
@@ -289,7 +286,6 @@ void vertexmain();
 
 
 void main() {
 void main() {
 	love_initializeBuiltinUniforms();
 	love_initializeBuiltinUniforms();
-	setPointSize();
 	vertexmain();
 	vertexmain();
 }
 }
 )";
 )";
@@ -604,13 +600,29 @@ TextureType Shader::getMainTextureType() const
 	return info != nullptr ? info->textureType : TEXTURE_MAX_ENUM;
 	return info != nullptr ? info->textureType : TEXTURE_MAX_ENUM;
 }
 }
 
 
-void Shader::checkMainTextureType(TextureType textype, bool isDepthSampler) const
+void Shader::validateDrawState(PrimitiveType primtype, Texture *maintex) const
 {
 {
+	if ((primtype == PRIMITIVE_POINTS) != validationReflection.usesPointSize)
+	{
+		if (validationReflection.usesPointSize)
+			throw love::Exception("The active shader can only be used to draw points.");
+		else
+			throw love::Exception("The gl_PointSize variable must be set in a vertex shader when drawing points.");
+	}
+
+	if (maintex == nullptr)
+		return;
+
 	const UniformInfo *info = getUniformInfo(BUILTIN_TEXTURE_MAIN);
 	const UniformInfo *info = getUniformInfo(BUILTIN_TEXTURE_MAIN);
 
 
 	if (info == nullptr)
 	if (info == nullptr)
 		return;
 		return;
 
 
+	if (!maintex->isReadable())
+		throw love::Exception("Textures with non-readable formats cannot be sampled from in a shader.");
+
+	auto textype = maintex->getTextureType();
+
 	if (info->textureType != TEXTURE_MAX_ENUM && info->textureType != textype)
 	if (info->textureType != TEXTURE_MAX_ENUM && info->textureType != textype)
 	{
 	{
 		const char *textypestr = "unknown";
 		const char *textypestr = "unknown";
@@ -620,7 +632,7 @@ void Shader::checkMainTextureType(TextureType textype, bool isDepthSampler) cons
 		throw love::Exception("Texture's type (%s) must match the type of the shader's main texture type (%s).", textypestr, shadertextypestr);
 		throw love::Exception("Texture's type (%s) must match the type of the shader's main texture type (%s).", textypestr, shadertextypestr);
 	}
 	}
 
 
-	if (info->isDepthSampler != isDepthSampler)
+	if (info->isDepthSampler != maintex->getSamplerState().depthSampleMode.hasValue)
 	{
 	{
 		if (info->isDepthSampler)
 		if (info->isDepthSampler)
 			throw love::Exception("Depth comparison samplers in shaders can only be used with depth textures which have depth comparison set.");
 			throw love::Exception("Depth comparison samplers in shaders can only be used with depth textures which have depth comparison set.");
@@ -629,14 +641,6 @@ void Shader::checkMainTextureType(TextureType textype, bool isDepthSampler) cons
 	}
 	}
 }
 }
 
 
-void Shader::checkMainTexture(Texture *tex) const
-{
-	if (!tex->isReadable())
-		throw love::Exception("Textures with non-readable formats cannot be sampled from in a shader.");
-
-	checkMainTextureType(tex->getTextureType(), tex->getSamplerState().depthSampleMode.hasValue);
-}
-
 bool Shader::validate(ShaderStage* vertex, ShaderStage* pixel, std::string& err)
 bool Shader::validate(ShaderStage* vertex, ShaderStage* pixel, std::string& err)
 {
 {
 	ValidationReflection reflection;
 	ValidationReflection reflection;
@@ -665,6 +669,13 @@ bool Shader::validateInternal(ShaderStage *vertex, ShaderStage *pixel, std::stri
 		return false;
 		return false;
 	}
 	}
 
 
+	const auto *vertintermediate = program.getIntermediate(EShLangVertex);
+	if (vertintermediate != nullptr)
+	{
+		// NOTE: this doesn't check whether the use affects final output...
+		reflection.usesPointSize = vertintermediate->inIoAccessed("gl_PointSize");
+	}
+
 	for (int i = 0; i < program.getNumBufferBlocks(); i++)
 	for (int i = 0; i < program.getNumBufferBlocks(); i++)
 	{
 	{
 		const glslang::TObjectReflection &info = program.getBufferBlock(i);
 		const glslang::TObjectReflection &info = program.getBufferBlock(i);
@@ -732,6 +743,14 @@ vec4 position(mat4 clipSpaceFromLocal, vec4 localPosition)
 }
 }
 )";
 )";
 
 
+static const std::string defaultPointsVertex = R"(
+vec4 position(mat4 clipSpaceFromLocal, vec4 localPosition)
+{
+	love_PointSize = ConstantPointSize * CurrentDPIScale;
+	return clipSpaceFromLocal * localPosition;
+}
+)";
+
 static const std::string defaultStandardPixel = R"(
 static const std::string defaultStandardPixel = R"(
 vec4 effect(vec4 vcolor, Image tex, vec2 texcoord, vec2 pixcoord)
 vec4 effect(vec4 vcolor, Image tex, vec2 texcoord, vec2 pixcoord)
 {
 {
@@ -757,7 +776,12 @@ void effect()
 const std::string &Shader::getDefaultCode(StandardShader shader, ShaderStage::StageType stage)
 const std::string &Shader::getDefaultCode(StandardShader shader, ShaderStage::StageType stage)
 {
 {
 	if (stage == ShaderStage::STAGE_VERTEX)
 	if (stage == ShaderStage::STAGE_VERTEX)
-		return defaultVertex;
+	{
+		if (shader == STANDARD_POINTS)
+			return defaultPointsVertex;
+		else
+			return defaultVertex;
+	}
 
 
 	static std::string nocode = "";
 	static std::string nocode = "";
 
 
@@ -766,6 +790,7 @@ const std::string &Shader::getDefaultCode(StandardShader shader, ShaderStage::St
 		case STANDARD_DEFAULT: return defaultStandardPixel;
 		case STANDARD_DEFAULT: return defaultStandardPixel;
 		case STANDARD_VIDEO: return defaultVideoPixel;
 		case STANDARD_VIDEO: return defaultVideoPixel;
 		case STANDARD_ARRAY: return defaultArrayPixel;
 		case STANDARD_ARRAY: return defaultArrayPixel;
+		case STANDARD_POINTS: return defaultStandardPixel;
 		case STANDARD_MAX_ENUM: return nocode;
 		case STANDARD_MAX_ENUM: return nocode;
 	}
 	}
 
 
@@ -788,7 +813,6 @@ static StringMap<Shader::BuiltinUniform, Shader::BUILTIN_MAX_ENUM>::Entry builti
 	{ "love_VideoCbChannel",  Shader::BUILTIN_TEXTURE_VIDEO_CB  },
 	{ "love_VideoCbChannel",  Shader::BUILTIN_TEXTURE_VIDEO_CB  },
 	{ "love_VideoCrChannel",  Shader::BUILTIN_TEXTURE_VIDEO_CR  },
 	{ "love_VideoCrChannel",  Shader::BUILTIN_TEXTURE_VIDEO_CR  },
 	{ "love_UniformsPerDraw", Shader::BUILTIN_UNIFORMS_PER_DRAW },
 	{ "love_UniformsPerDraw", Shader::BUILTIN_UNIFORMS_PER_DRAW },
-	{ "love_PointSize",       Shader::BUILTIN_POINT_SIZE        },
 };
 };
 
 
 static StringMap<Shader::BuiltinUniform, Shader::BUILTIN_MAX_ENUM> builtinNames(builtinNameEntries, sizeof(builtinNameEntries));
 static StringMap<Shader::BuiltinUniform, Shader::BUILTIN_MAX_ENUM> builtinNames(builtinNameEntries, sizeof(builtinNameEntries));

+ 3 - 3
src/modules/graphics/Shader.h

@@ -64,7 +64,6 @@ public:
 		BUILTIN_TEXTURE_VIDEO_CB,
 		BUILTIN_TEXTURE_VIDEO_CB,
 		BUILTIN_TEXTURE_VIDEO_CR,
 		BUILTIN_TEXTURE_VIDEO_CR,
 		BUILTIN_UNIFORMS_PER_DRAW,
 		BUILTIN_UNIFORMS_PER_DRAW,
-		BUILTIN_POINT_SIZE,
 		BUILTIN_MAX_ENUM
 		BUILTIN_MAX_ENUM
 	};
 	};
 
 
@@ -88,6 +87,7 @@ public:
 		STANDARD_DEFAULT,
 		STANDARD_DEFAULT,
 		STANDARD_VIDEO,
 		STANDARD_VIDEO,
 		STANDARD_ARRAY,
 		STANDARD_ARRAY,
+		STANDARD_POINTS,
 		STANDARD_MAX_ENUM
 		STANDARD_MAX_ENUM
 	};
 	};
 
 
@@ -209,8 +209,7 @@ public:
 	virtual void setVideoTextures(Texture *ytexture, Texture *cbtexture, Texture *crtexture) = 0;
 	virtual void setVideoTextures(Texture *ytexture, Texture *cbtexture, Texture *crtexture) = 0;
 
 
 	TextureType getMainTextureType() const;
 	TextureType getMainTextureType() const;
-	void checkMainTextureType(TextureType textype, bool isDepthSampler) const;
-	void checkMainTexture(Texture *texture) const;
+	void validateDrawState(PrimitiveType primtype, Texture *maintexture) const;
 
 
 	static SourceInfo getSourceInfo(const std::string &src);
 	static SourceInfo getSourceInfo(const std::string &src);
 	static std::string createShaderStageCode(Graphics *gfx, ShaderStage::StageType stage, const std::string &code, const SourceInfo &info);
 	static std::string createShaderStageCode(Graphics *gfx, ShaderStage::StageType stage, const std::string &code, const SourceInfo &info);
@@ -239,6 +238,7 @@ protected:
 	struct ValidationReflection
 	struct ValidationReflection
 	{
 	{
 		std::map<std::string, BufferReflection> storageBuffers;
 		std::map<std::string, BufferReflection> storageBuffers;
+		bool usesPointSize;
 	};
 	};
 
 
 	static bool validateInternal(ShaderStage* vertex, ShaderStage* pixel, std::string& err, ValidationReflection &reflection);
 	static bool validateInternal(ShaderStage* vertex, ShaderStage* pixel, std::string& err, ValidationReflection &reflection);

+ 8 - 8
src/modules/graphics/SpriteBatch.cpp

@@ -40,7 +40,7 @@ namespace graphics
 
 
 love::Type SpriteBatch::type("SpriteBatch", &Drawable::type);
 love::Type SpriteBatch::type("SpriteBatch", &Drawable::type);
 
 
-SpriteBatch::SpriteBatch(Graphics *gfx, Texture *texture, int size, BufferUsage usage)
+SpriteBatch::SpriteBatch(Graphics *gfx, Texture *texture, int size, BufferDataUsage usage)
 	: texture(texture)
 	: texture(texture)
 	, size(size)
 	, size(size)
 	, next(0)
 	, next(0)
@@ -73,7 +73,7 @@ SpriteBatch::SpriteBatch(Graphics *gfx, Texture *texture, int size, BufferUsage
 
 
 	memset(vertex_data, 0, vertex_size);
 	memset(vertex_data, 0, vertex_size);
 
 
-	Buffer::Settings settings(Buffer::TYPEFLAG_VERTEX, usage);
+	Buffer::Settings settings(BUFFERUSAGEFLAG_VERTEX, usage);
 	auto decl = Buffer::getCommonFormatDeclaration(vertex_format);
 	auto decl = Buffer::getCommonFormatDeclaration(vertex_format);
 
 
 	array_buf.set(gfx->newBuffer(settings, decl, nullptr, vertex_size, 0), Acquire::NORETAIN);
 	array_buf.set(gfx->newBuffer(settings, decl, nullptr, vertex_size, 0), Acquire::NORETAIN);
@@ -185,7 +185,7 @@ void SpriteBatch::flush()
 		size_t offset = modified_sprites.getOffset() * vertex_stride * 4;
 		size_t offset = modified_sprites.getOffset() * vertex_stride * 4;
 		size_t size = modified_sprites.getSize() * vertex_stride * 4;
 		size_t size = modified_sprites.getSize() * vertex_stride * 4;
 
 
-		if (array_buf->getUsage() == BUFFERUSAGE_STREAM)
+		if (array_buf->getDataUsage() == BUFFERDATAUSAGE_STREAM)
 			array_buf->fill(0, array_buf->getSize(), vertex_data);
 			array_buf->fill(0, array_buf->getSize(), vertex_data);
 		else
 		else
 			array_buf->fill(offset, size, vertex_data + offset);
 			array_buf->fill(offset, size, vertex_data + offset);
@@ -244,7 +244,7 @@ void SpriteBatch::setBufferSize(int newsize)
 		throw love::Exception("Out of memory.");
 		throw love::Exception("Out of memory.");
 
 
 	auto gfx = Module::getInstance<graphics::Graphics>(Module::M_GRAPHICS);
 	auto gfx = Module::getInstance<graphics::Graphics>(Module::M_GRAPHICS);
-	Buffer::Settings settings(array_buf->getTypeFlags(), array_buf->getUsage());
+	Buffer::Settings settings(array_buf->getUsageFlags(), array_buf->getDataUsage());
 	auto decl = Buffer::getCommonFormatDeclaration(vertex_format);
 	auto decl = Buffer::getCommonFormatDeclaration(vertex_format);
 
 
 	array_buf.set(gfx->newBuffer(settings, decl, nullptr, vertex_size, 0), Acquire::NORETAIN);
 	array_buf.set(gfx->newBuffer(settings, decl, nullptr, vertex_size, 0), Acquire::NORETAIN);
@@ -264,7 +264,7 @@ int SpriteBatch::getBufferSize() const
 
 
 void SpriteBatch::attachAttribute(const std::string &name, Buffer *buffer, Mesh *mesh)
 void SpriteBatch::attachAttribute(const std::string &name, Buffer *buffer, Mesh *mesh)
 {
 {
-	if ((buffer->getTypeFlags() & Buffer::TYPEFLAG_VERTEX) == 0)
+	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.");
 		throw love::Exception("GraphicsBuffer must be created with vertex buffer support to be used as a SpriteBatch vertex attribute.");
 
 
 	AttachedAttribute oldattrib = {};
 	AttachedAttribute oldattrib = {};
@@ -329,11 +329,11 @@ void SpriteBatch::draw(Graphics *gfx, const Matrix4 &m)
 
 
 			Shader::attachDefault(defaultshader);
 			Shader::attachDefault(defaultshader);
 		}
 		}
-
-		if (Shader::current)
-			Shader::current->checkMainTexture(texture);
 	}
 	}
 
 
+	if (Shader::current)
+		Shader::current->validateDrawState(PRIMITIVE_TRIANGLES, texture);
+
 	flush(); // Upload any modified sprite data to the GPU.
 	flush(); // Upload any modified sprite data to the GPU.
 
 
 	VertexAttributes attributes;
 	VertexAttributes attributes;

+ 1 - 1
src/modules/graphics/SpriteBatch.h

@@ -52,7 +52,7 @@ public:
 
 
 	static love::Type type;
 	static love::Type type;
 
 
-	SpriteBatch(Graphics *gfx, Texture *texture, int size, BufferUsage usage);
+	SpriteBatch(Graphics *gfx, Texture *texture, int size, BufferDataUsage usage);
 	virtual ~SpriteBatch();
 	virtual ~SpriteBatch();
 
 
 	int add(const Matrix4 &m, int index = -1);
 	int add(const Matrix4 &m, int index = -1);

+ 1 - 1
src/modules/graphics/StreamBuffer.cpp

@@ -26,7 +26,7 @@ namespace love
 namespace graphics
 namespace graphics
 {
 {
 
 
-StreamBuffer::StreamBuffer(BufferType mode, size_t size)
+StreamBuffer::StreamBuffer(BufferUsage mode, size_t size)
 	: bufferSize(size)
 	: bufferSize(size)
 	, frameGPUReadOffset(0)
 	, frameGPUReadOffset(0)
 	, mode(mode)
 	, mode(mode)

+ 3 - 3
src/modules/graphics/StreamBuffer.h

@@ -53,7 +53,7 @@ public:
 	virtual ~StreamBuffer() {}
 	virtual ~StreamBuffer() {}
 
 
 	size_t getSize() const { return bufferSize; }
 	size_t getSize() const { return bufferSize; }
-	BufferType getMode() const { return mode; }
+	BufferUsage getMode() const { return mode; }
 	size_t getUsableSize() const { return bufferSize - frameGPUReadOffset; }
 	size_t getUsableSize() const { return bufferSize - frameGPUReadOffset; }
 
 
 	virtual MapInfo map(size_t minsize) = 0;
 	virtual MapInfo map(size_t minsize) = 0;
@@ -64,11 +64,11 @@ public:
 
 
 protected:
 protected:
 
 
-	StreamBuffer(BufferType mode, size_t size);
+	StreamBuffer(BufferUsage mode, size_t size);
 
 
 	size_t bufferSize;
 	size_t bufferSize;
 	size_t frameGPUReadOffset;
 	size_t frameGPUReadOffset;
-	BufferType mode;
+	BufferUsage mode;
 
 
 }; // StreamBuffer
 }; // StreamBuffer
 
 

+ 7 - 3
src/modules/graphics/Text.cpp

@@ -63,7 +63,7 @@ void Text::uploadVertices(const std::vector<Font::GlyphVertex> &vertices, size_t
 
 
 		auto gfx = Module::getInstance<Graphics>(Module::M_GRAPHICS);
 		auto gfx = Module::getInstance<Graphics>(Module::M_GRAPHICS);
 
 
-		Buffer::Settings settings(Buffer::TYPEFLAG_VERTEX, BUFFERUSAGE_DYNAMIC);
+		Buffer::Settings settings(BUFFERUSAGEFLAG_VERTEX, BUFFERDATAUSAGE_DYNAMIC);
 		auto decl = Buffer::getCommonFormatDeclaration(Font::vertexFormat);
 		auto decl = Buffer::getCommonFormatDeclaration(Font::vertexFormat);
 		Buffer *newbuffer = gfx->newBuffer(settings, decl, nullptr, newsize, 0);
 		Buffer *newbuffer = gfx->newBuffer(settings, decl, nullptr, newsize, 0);
 
 
@@ -262,8 +262,12 @@ void Text::draw(Graphics *gfx, const Matrix4 &m)
 	if (Shader::isDefaultActive())
 	if (Shader::isDefaultActive())
 		Shader::attachDefault(Shader::STANDARD_DEFAULT);
 		Shader::attachDefault(Shader::STANDARD_DEFAULT);
 
 
+	Texture *firsttex = nullptr;
+	if (!drawCommands.empty())
+		firsttex = drawCommands[0].texture;
+
 	if (Shader::current)
 	if (Shader::current)
-		Shader::current->checkMainTextureType(TEXTURE_2D, false);
+		Shader::current->validateDrawState(PRIMITIVE_TRIANGLES, firsttex);
 
 
 	// Re-generate the text if the Font's texture cache was invalidated.
 	// Re-generate the text if the Font's texture cache was invalidated.
 	if (font->getTextureCacheID() != textureCacheID)
 	if (font->getTextureCacheID() != textureCacheID)
@@ -279,7 +283,7 @@ void Text::draw(Graphics *gfx, const Matrix4 &m)
 		size_t offset = modifiedVertices.getOffset();
 		size_t offset = modifiedVertices.getOffset();
 		size_t size = modifiedVertices.getSize();
 		size_t size = modifiedVertices.getSize();
 
 
-		if (vertexBuffer->getUsage() == BUFFERUSAGE_STREAM)
+		if (vertexBuffer->getDataUsage() == BUFFERDATAUSAGE_STREAM)
 			vertexBuffer->fill(0, vertexBuffer->getSize(), vertexData);
 			vertexBuffer->fill(0, vertexBuffer->getSize(), vertexData);
 		else
 		else
 			vertexBuffer->fill(offset, size, vertexData + offset);
 			vertexBuffer->fill(offset, size, vertexData + offset);

+ 3 - 3
src/modules/graphics/Texture.cpp

@@ -223,7 +223,7 @@ Texture::Texture(Graphics *gfx, const Settings &settings, const Slices *slices)
 	else
 	else
 		readable = !renderTarget || !isPixelFormatDepthStencil(format);
 		readable = !renderTarget || !isPixelFormatDepthStencil(format);
 
 
-	format = gfx->getSizedFormat(format, renderTarget, readable, sRGB);
+	format = gfx->getSizedFormat(format, renderTarget, readable);
 
 
 	if (mipmapsMode == MIPMAPS_AUTO && isCompressed())
 	if (mipmapsMode == MIPMAPS_AUTO && isCompressed())
 		mipmapsMode = MIPMAPS_MANUAL;
 		mipmapsMode = MIPMAPS_MANUAL;
@@ -419,7 +419,7 @@ void Texture::uploadImageData(love::image::ImageDataBase *d, int level, int slic
 		lock.setLock(id->getMutex());
 		lock.setLock(id->getMutex());
 
 
 	Rect rect = {x, y, d->getWidth(), d->getHeight()};
 	Rect rect = {x, y, d->getWidth(), d->getHeight()};
-	uploadByteData(d->getFormat(), d->getData(), d->getSize(), level, slice, rect, d);
+	uploadByteData(d->getFormat(), d->getData(), d->getSize(), level, slice, rect);
 }
 }
 
 
 void Texture::replacePixels(love::image::ImageDataBase *d, int slice, int mipmap, int x, int y, bool reloadmipmaps)
 void Texture::replacePixels(love::image::ImageDataBase *d, int slice, int mipmap, int x, int y, bool reloadmipmaps)
@@ -485,7 +485,7 @@ void Texture::replacePixels(const void *data, size_t size, int slice, int mipmap
 
 
 	Graphics::flushBatchedDrawsGlobal();
 	Graphics::flushBatchedDrawsGlobal();
 
 
-	uploadByteData(format, data, size, mipmap, slice, rect, nullptr);
+	uploadByteData(format, data, size, mipmap, slice, rect);
 
 
 	if (reloadmipmaps && mipmap == 0 && getMipmapCount() > 1)
 	if (reloadmipmaps && mipmap == 0 && getMipmapCount() > 1)
 		generateMipmaps();
 		generateMipmaps();

+ 21 - 1
src/modules/graphics/Texture.h

@@ -56,6 +56,26 @@ enum TextureType
 	TEXTURE_MAX_ENUM
 	TEXTURE_MAX_ENUM
 };
 };
 
 
+enum PixelFormatUsage
+{
+	PIXELFORMATUSAGE_SAMPLE,
+	PIXELFORMATUSAGE_LINEAR,
+	PIXELFORMATUSAGE_RENDERTARGET,
+	PIXELFORMATUSAGE_BLEND,
+	PIXELFORMATUSAGE_MSAA,
+	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),
+};
+
 struct SamplerState
 struct SamplerState
 {
 {
 	enum WrapMode
 	enum WrapMode
@@ -276,7 +296,7 @@ protected:
 	void setGraphicsMemorySize(int64 size);
 	void setGraphicsMemorySize(int64 size);
 
 
 	void uploadImageData(love::image::ImageDataBase *d, int level, int slice, int x, int y);
 	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, love::image::ImageDataBase *imgd = nullptr) = 0;
+	virtual void uploadByteData(PixelFormat pixelformat, const void *data, size_t size, int level, int slice, const Rect &r) = 0;
 
 
 	virtual void generateMipmapsInternal() = 0;
 	virtual void generateMipmapsInternal() = 0;
 	virtual void readbackImageData(love::image::ImageData *imagedata, int slice, int mipmap, const Rect &rect) = 0;
 	virtual void readbackImageData(love::image::ImageData *imagedata, int slice, int mipmap, const Rect &rect) = 0;

+ 1 - 0
src/modules/graphics/metal/Buffer.h

@@ -41,6 +41,7 @@ public:
 	void *map(MapType map, size_t offset, size_t size) override;
 	void *map(MapType map, size_t offset, size_t size) override;
 	void unmap(size_t usedoffset, size_t usedsize) override;
 	void unmap(size_t usedoffset, size_t usedsize) override;
 	void fill(size_t offset, size_t size, const void *data) override;
 	void fill(size_t offset, size_t size, const void *data) override;
+	void copyTo(love::graphics::Buffer *dest, size_t sourceoffset, size_t destoffset, size_t size) override;
 
 
 	ptrdiff_t getHandle() const override { return (ptrdiff_t) buffer; }
 	ptrdiff_t getHandle() const override { return (ptrdiff_t) buffer; }
 	ptrdiff_t getTexelBufferHandle() const override { return (ptrdiff_t) texture; }
 	ptrdiff_t getTexelBufferHandle() const override { return (ptrdiff_t) texture; }

+ 13 - 1
src/modules/graphics/metal/Buffer.mm

@@ -71,7 +71,7 @@ Buffer::Buffer(love::graphics::Graphics *gfx, id<MTLDevice> device, const Settin
 	if (buffer == nil)
 	if (buffer == nil)
 		throw love::Exception("Could not create buffer (out of VRAM?)");
 		throw love::Exception("Could not create buffer (out of VRAM?)");
 
 
-	if (typeFlags & TYPEFLAG_TEXEL)
+	if (usageFlags & BUFFERUSAGEFLAG_TEXEL)
 	{
 	{
 		if (@available(iOS 12, macOS 10.14, *))
 		if (@available(iOS 12, macOS 10.14, *))
 		{
 		{
@@ -169,6 +169,18 @@ void Buffer::fill(size_t offset, size_t size, const void *data)
 					   size:size];
 					   size:size];
 }}
 }}
 
 
+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
 } // metal
 } // graphics
 } // graphics
 } // love
 } // love

+ 2 - 2
src/modules/graphics/metal/Graphics.h

@@ -90,7 +90,7 @@ public:
 
 
 	void setWireframe(bool enable) override;
 	void setWireframe(bool enable) override;
 	
 	
-	PixelFormat getSizedFormat(PixelFormat format, bool rendertarget, bool readable, bool sRGB) const override;
+	PixelFormat getSizedFormat(PixelFormat format, bool rendertarget, bool readable) const override;
 	bool isPixelFormatSupported(PixelFormat format, bool rendertarget, bool readable, bool sRGB = false) override;
 	bool isPixelFormatSupported(PixelFormat format, bool rendertarget, bool readable, bool sRGB = false) override;
 	Renderer getRenderer() const override;
 	Renderer getRenderer() const override;
 	bool usesGLSLES() const override;
 	bool usesGLSLES() const override;
@@ -163,7 +163,7 @@ private:
 
 
 	love::graphics::ShaderStage *newShaderStageInternal(ShaderStage::StageType stage, const std::string &cachekey, const std::string &source, bool gles) override;
 	love::graphics::ShaderStage *newShaderStageInternal(ShaderStage::StageType stage, const std::string &cachekey, const std::string &source, bool gles) override;
 	love::graphics::Shader *newShaderInternal(love::graphics::ShaderStage *vertex, love::graphics::ShaderStage *pixel) override;
 	love::graphics::Shader *newShaderInternal(love::graphics::ShaderStage *vertex, love::graphics::ShaderStage *pixel) override;
-	love::graphics::StreamBuffer *newStreamBuffer(BufferType type, size_t size) override;
+	love::graphics::StreamBuffer *newStreamBuffer(BufferUsage usage, size_t size) override;
 	void setRenderTargetsInternal(const RenderTargets &rts, int w, int h, int pixelw, int pixelh, bool hasSRGBcanvas) override;
 	void setRenderTargetsInternal(const RenderTargets &rts, int w, int h, int pixelw, int pixelh, bool hasSRGBcanvas) override;
 	void initCapabilities() override;
 	void initCapabilities() override;
 	void getAPIStats(int &shaderswitches) const override;
 	void getAPIStats(int &shaderswitches) const override;

+ 11 - 10
src/modules/graphics/metal/Graphics.mm

@@ -189,7 +189,7 @@ Graphics::Graphics()
 
 
 	initCapabilities();
 	initCapabilities();
 
 
-	uniformBuffer = CreateStreamBuffer(device, BUFFERTYPE_UNIFORM, 1024 * 1024 * 1);
+	uniformBuffer = CreateStreamBuffer(device, BUFFERUSAGE_UNIFORM, 1024 * 1024 * 1);
 
 
 	{
 	{
 		std::vector<Buffer::DataDeclaration> dataformat = {
 		std::vector<Buffer::DataDeclaration> dataformat = {
@@ -202,7 +202,7 @@ Graphics::Graphics()
 			{0, 0, 0, 1},
 			{0, 0, 0, 1},
 		};
 		};
 
 
-		Buffer::Settings attribsettings(Buffer::TYPEFLAG_VERTEX, BUFFERUSAGE_STATIC);
+		Buffer::Settings attribsettings(BUFFERUSAGEFLAG_VERTEX, BUFFERDATAUSAGE_STATIC);
 
 
 		defaultAttributesBuffer = newBuffer(attribsettings, dataformat, &defaults, sizeof(DefaultVertexAttributes), 0);
 		defaultAttributesBuffer = newBuffer(attribsettings, dataformat, &defaults, sizeof(DefaultVertexAttributes), 0);
 	}
 	}
@@ -261,9 +261,9 @@ Graphics::~Graphics()
 	graphicsInstance = nullptr;
 	graphicsInstance = nullptr;
 }}
 }}
 
 
-love::graphics::StreamBuffer *Graphics::newStreamBuffer(BufferType type, size_t size)
+love::graphics::StreamBuffer *Graphics::newStreamBuffer(BufferUsage usage, size_t size)
 {
 {
-	return CreateStreamBuffer(device, type, size);
+	return CreateStreamBuffer(device, usage, size);
 }
 }
 
 
 love::graphics::Texture *Graphics::newTexture(const Texture::Settings &settings, const Texture::Slices *data)
 love::graphics::Texture *Graphics::newTexture(const Texture::Settings &settings, const Texture::Slices *data)
@@ -324,9 +324,9 @@ bool Graphics::setMode(void *context, int width, int height, int pixelwidth, int
 	{
 	{
 		// Initial sizes that should be good enough for most cases. It will
 		// Initial sizes that should be good enough for most cases. It will
 		// resize to fit if needed, later.
 		// resize to fit if needed, later.
-		batchedDrawState.vb[0] = CreateStreamBuffer(device, BUFFERTYPE_VERTEX, 1024 * 1024 * 1);
-		batchedDrawState.vb[1] = CreateStreamBuffer(device, BUFFERTYPE_VERTEX, 256  * 1024 * 1);
-		batchedDrawState.indexBuffer = CreateStreamBuffer(device, BUFFERTYPE_INDEX, sizeof(uint16) * LOVE_UINT16_MAX);
+		batchedDrawState.vb[0] = CreateStreamBuffer(device, BUFFERUSAGE_VERTEX, 1024 * 1024 * 1);
+		batchedDrawState.vb[1] = CreateStreamBuffer(device, BUFFERUSAGE_VERTEX, 256  * 1024 * 1);
+		batchedDrawState.indexBuffer = CreateStreamBuffer(device, BUFFERUSAGE_INDEX, sizeof(uint16) * LOVE_UINT16_MAX);
 	}
 	}
 
 
 	createQuadIndexBuffer();
 	createQuadIndexBuffer();
@@ -717,7 +717,7 @@ void Graphics::applyShaderUniforms(id<MTLRenderCommandEncoder> renderEncoder, lo
 	{
 	{
 		size_t newsize = uniformBuffer->getSize() * 2;
 		size_t newsize = uniformBuffer->getSize() * 2;
 		delete uniformBuffer;
 		delete uniformBuffer;
-		uniformBuffer = CreateStreamBuffer(device, BUFFERTYPE_UNIFORM, newsize);
+		uniformBuffer = CreateStreamBuffer(device, BUFFERUSAGE_UNIFORM, newsize);
 		uniformBufferData = {};
 		uniformBufferData = {};
 		uniformBufferOffset = 0;
 		uniformBufferOffset = 0;
 	}
 	}
@@ -1293,7 +1293,7 @@ void Graphics::setWireframe(bool enable)
 	}
 	}
 }
 }
 
 
-PixelFormat Graphics::getSizedFormat(PixelFormat format, bool /*rendertarget*/, bool /*readable*/, bool /*sRGB*/) const
+PixelFormat Graphics::getSizedFormat(PixelFormat format, bool /*rendertarget*/, bool /*readable*/) const
 {
 {
 	switch (format)
 	switch (format)
 	{
 	{
@@ -1362,7 +1362,8 @@ void Graphics::initCapabilities()
 	capabilities.features[FEATURE_GLSL4] = true;
 	capabilities.features[FEATURE_GLSL4] = true;
 	capabilities.features[FEATURE_INSTANCING] = true;
 	capabilities.features[FEATURE_INSTANCING] = true;
 	capabilities.features[FEATURE_TEXEL_BUFFER] = true;
 	capabilities.features[FEATURE_TEXEL_BUFFER] = true;
-	static_assert(FEATURE_MAX_ENUM == 11, "Graphics::initCapabilities must be updated when adding a new graphics feature!");
+	capabilities.features[FEATURE_COPY_BUFFER] = true;
+	static_assert(FEATURE_MAX_ENUM == 12, "Graphics::initCapabilities must be updated when adding a new graphics feature!");
 
 
 	// https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf
 	// https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf
 	capabilities.limits[LIMIT_POINT_SIZE] = 511;
 	capabilities.limits[LIMIT_POINT_SIZE] = 511;

+ 1 - 1
src/modules/graphics/metal/StreamBuffer.h

@@ -30,7 +30,7 @@ namespace graphics
 namespace metal
 namespace metal
 {
 {
 
 
-love::graphics::StreamBuffer *CreateStreamBuffer(id<MTLDevice> device, BufferType mode, size_t size);
+love::graphics::StreamBuffer *CreateStreamBuffer(id<MTLDevice> device, BufferUsage usage, size_t size);
 
 
 } // metal
 } // metal
 } // graphics
 } // graphics

+ 4 - 4
src/modules/graphics/metal/StreamBuffer.mm

@@ -38,8 +38,8 @@ class StreamBuffer final : public love::graphics::StreamBuffer
 {
 {
 public:
 public:
 
 
-	StreamBuffer(id<MTLDevice> device, BufferType mode, size_t size)
-		: love::graphics::StreamBuffer(mode, size)
+	StreamBuffer(id<MTLDevice> device, BufferUsage usage, size_t size)
+		: love::graphics::StreamBuffer(usage, size)
 		, frameIndex(0)
 		, frameIndex(0)
 		, mappedFrames()
 		, mappedFrames()
 	{ @autoreleasepool {
 	{ @autoreleasepool {
@@ -122,9 +122,9 @@ private:
 
 
 }; // StreamBuffer
 }; // StreamBuffer
 
 
-love::graphics::StreamBuffer *CreateStreamBuffer(id<MTLDevice> device, BufferType mode, size_t size)
+love::graphics::StreamBuffer *CreateStreamBuffer(id<MTLDevice> device, BufferUsage usage, size_t size)
 {
 {
-	return new StreamBuffer(device, mode, size);
+	return new StreamBuffer(device, usage, size);
 }
 }
 
 
 } // metal
 } // metal

+ 1 - 1
src/modules/graphics/metal/Texture.h

@@ -51,7 +51,7 @@ public:
 
 
 private:
 private:
 
 
-	void uploadByteData(PixelFormat pixelformat, const void *data, size_t size, int level, int slice, const Rect &r, love::image::ImageDataBase *imgd = nullptr) override;
+	void uploadByteData(PixelFormat pixelformat, const void *data, size_t size, int level, int slice, const Rect &r) override;
 	void generateMipmapsInternal() override;
 	void generateMipmapsInternal() override;
 	void readbackImageData(love::image::ImageData *imagedata, int slice, int mipmap, const Rect &rect) override;
 	void readbackImageData(love::image::ImageData *imagedata, int slice, int mipmap, const Rect &rect) override;
 
 

+ 1 - 1
src/modules/graphics/metal/Texture.mm

@@ -142,7 +142,7 @@ Texture::~Texture()
 	sampler = nil;
 	sampler = nil;
 }}
 }}
 
 
-void Texture::uploadByteData(PixelFormat pixelformat, const void *data, size_t size, int level, int slice, const Rect &r, love::image::ImageDataBase *)
+void Texture::uploadByteData(PixelFormat pixelformat, const void *data, size_t size, int level, int slice, const Rect &r)
 { @autoreleasepool {
 { @autoreleasepool {
 	auto gfx = Graphics::getInstance();
 	auto gfx = Graphics::getInstance();
 	id<MTLBuffer> buffer = [gfx->device newBufferWithBytes:data
 	id<MTLBuffer> buffer = [gfx->device newBufferWithBytes:data

+ 36 - 22
src/modules/graphics/opengl/Buffer.cpp

@@ -72,18 +72,22 @@ Buffer::Buffer(love::graphics::Graphics *gfx, const Settings &settings, const st
 	size = getSize();
 	size = getSize();
 	arraylength = getArrayLength();
 	arraylength = getArrayLength();
 
 
-	if (typeFlags & TYPEFLAG_TEXEL)
-		mapType = BUFFERTYPE_TEXEL;
-	else if (typeFlags & TYPEFLAG_VERTEX)
-		mapType = BUFFERTYPE_VERTEX;
-	else if (typeFlags & TYPEFLAG_INDEX)
-		mapType = BUFFERTYPE_INDEX;
-	else  if (typeFlags & TYPEFLAG_SHADER_STORAGE)
-		mapType = BUFFERTYPE_SHADER_STORAGE;
-
-	target = OpenGL::getGLBufferType(mapType);
-
-	if (usage == BUFFERUSAGE_STREAM)
+	if (usageFlags & BUFFERUSAGEFLAG_TEXEL)
+		mapUsage = BUFFERUSAGE_TEXEL;
+	else if (usageFlags & BUFFERUSAGEFLAG_VERTEX)
+		mapUsage = BUFFERUSAGE_VERTEX;
+	else if (usageFlags & BUFFERUSAGEFLAG_INDEX)
+		mapUsage = BUFFERUSAGE_INDEX;
+	else  if (usageFlags & BUFFERUSAGEFLAG_SHADER_STORAGE)
+		mapUsage = BUFFERUSAGE_SHADER_STORAGE;
+	else if (usageFlags & BUFFERUSAGEFLAG_COPY_SOURCE)
+		mapUsage = BUFFERUSAGE_COPY_SOURCE;
+	else if (usageFlags & BUFFERUSAGEFLAG_COPY_DEST)
+		mapUsage = BUFFERUSAGE_COPY_DEST;
+
+	target = OpenGL::getGLBufferType(mapUsage);
+
+	if (dataUsage == BUFFERDATAUSAGE_STREAM)
 		ownsMemoryMap = true;
 		ownsMemoryMap = true;
 
 
 	std::vector<uint8> emptydata;
 	std::vector<uint8> emptydata;
@@ -139,12 +143,14 @@ bool Buffer::load(const void *initialdata)
 		/* Clear the error buffer. */;
 		/* Clear the error buffer. */;
 
 
 	glGenBuffers(1, &buffer);
 	glGenBuffers(1, &buffer);
-	gl.bindBuffer(mapType, buffer);
+	gl.bindBuffer(mapUsage, buffer);
+
+	GLenum gldatausage = OpenGL::getGLBufferDataUsage(getDataUsage());
 
 
 	// initialdata can be null.
 	// initialdata can be null.
-	glBufferData(target, (GLsizeiptr) getSize(), initialdata, OpenGL::getGLBufferUsage(getUsage()));
+	glBufferData(target, (GLsizeiptr) getSize(), initialdata, gldatausage);
 
 
-	if (getTypeFlags() & TYPEFLAG_TEXEL)
+	if (getUsageFlags() & BUFFERUSAGEFLAG_TEXEL)
 	{
 	{
 		glGenTextures(1, &texture);
 		glGenTextures(1, &texture);
 		gl.bindBufferTextureToUnit(texture, 0, false, true);
 		gl.bindBufferTextureToUnit(texture, 0, false, true);
@@ -200,7 +206,7 @@ void Buffer::unmap(size_t usedoffset, size_t usedsize)
 	mapped = false;
 	mapped = false;
 
 
 	// Orphan optimization - see fill().
 	// Orphan optimization - see fill().
-	if (usage != BUFFERUSAGE_STATIC && mappedRange.first == 0 && mappedRange.getSize() == getSize())
+	if (dataUsage != BUFFERDATAUSAGE_STATIC && mappedRange.first == 0 && mappedRange.getSize() == getSize())
 	{
 	{
 		usedoffset = 0;
 		usedoffset = 0;
 		usedsize = getSize();
 		usedsize = getSize();
@@ -228,21 +234,21 @@ void Buffer::fill(size_t offset, size_t size, const void *data)
 	if (!Range(0, buffersize).contains(Range(offset, size)))
 	if (!Range(0, buffersize).contains(Range(offset, size)))
 		return;
 		return;
 
 
-	GLenum glusage = OpenGL::getGLBufferUsage(usage);
+	GLenum gldatausage = OpenGL::getGLBufferDataUsage(dataUsage);
 
 
-	gl.bindBuffer(mapType, buffer);
+	gl.bindBuffer(mapUsage, buffer);
 
 
-	if (usage != BUFFERUSAGE_STATIC && size == buffersize)
+	if (dataUsage != BUFFERDATAUSAGE_STATIC && size == buffersize)
 	{
 	{
 		// "orphan" current buffer to avoid implicit synchronisation on the GPU:
 		// "orphan" current buffer to avoid implicit synchronisation on the GPU:
 		// http://www.seas.upenn.edu/~pcozzi/OpenGLInsights/OpenGLInsights-AsynchronousBufferTransfers.pdf
 		// http://www.seas.upenn.edu/~pcozzi/OpenGLInsights/OpenGLInsights-AsynchronousBufferTransfers.pdf
-		gl.bindBuffer(mapType, buffer);
-		glBufferData(target, (GLsizeiptr) buffersize, nullptr, glusage);
+		gl.bindBuffer(mapUsage, buffer);
+		glBufferData(target, (GLsizeiptr) buffersize, nullptr, gldatausage);
 
 
 #if LOVE_WINDOWS
 #if LOVE_WINDOWS
 		// TODO: Verify that this codepath is a useful optimization.
 		// TODO: Verify that this codepath is a useful optimization.
 		if (gl.getVendor() == OpenGL::VENDOR_INTEL)
 		if (gl.getVendor() == OpenGL::VENDOR_INTEL)
-			glBufferData(target, (GLsizeiptr) buffersize, data, glusage);
+			glBufferData(target, (GLsizeiptr) buffersize, data, gldatausage);
 		else
 		else
 #endif
 #endif
 			glBufferSubData(target, 0, (GLsizeiptr) buffersize, data);
 			glBufferSubData(target, 0, (GLsizeiptr) buffersize, data);
@@ -253,6 +259,14 @@ void Buffer::fill(size_t offset, size_t size, const void *data)
 	}
 	}
 }
 }
 
 
+void Buffer::copyTo(love::graphics::Buffer *dest, size_t sourceoffset, size_t destoffset, size_t size)
+{
+	gl.bindBuffer(BUFFERUSAGE_COPY_SOURCE, buffer);
+	gl.bindBuffer(BUFFERUSAGE_COPY_DEST, ((Buffer *) dest)->buffer);
+
+	glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, sourceoffset, destoffset, size);
+}
+
 } // opengl
 } // opengl
 } // graphics
 } // graphics
 } // love
 } // love

+ 2 - 1
src/modules/graphics/opengl/Buffer.h

@@ -53,6 +53,7 @@ public:
 	void *map(MapType map, size_t offset, size_t size) override;
 	void *map(MapType map, size_t offset, size_t size) override;
 	void unmap(size_t usedoffset, size_t usedsize) override;
 	void unmap(size_t usedoffset, size_t usedsize) override;
 	void fill(size_t offset, size_t size, const void *data) override;
 	void fill(size_t offset, size_t size, const void *data) override;
+	void copyTo(love::graphics::Buffer *dest, size_t sourceoffset, size_t destoffset, size_t size) override;
 
 
 	ptrdiff_t getHandle() const override { return buffer; };
 	ptrdiff_t getHandle() const override { return buffer; };
 	ptrdiff_t getTexelBufferHandle() const override { return texture; };
 	ptrdiff_t getTexelBufferHandle() const override { return texture; };
@@ -64,7 +65,7 @@ private:
 	void unmapStatic(size_t offset, size_t size);
 	void unmapStatic(size_t offset, size_t size);
 	void unmapStream();
 	void unmapStream();
 
 
-	BufferType mapType = BUFFERTYPE_VERTEX;
+	BufferUsage mapUsage = BUFFERUSAGE_VERTEX;
 	GLenum target = 0;
 	GLenum target = 0;
 
 
 	// The buffer object identifier. Assigned by OpenGL.
 	// The buffer object identifier. Assigned by OpenGL.

+ 44 - 27
src/modules/graphics/opengl/Graphics.cpp

@@ -157,7 +157,7 @@ const char *Graphics::getName() const
 	return "love.graphics.opengl";
 	return "love.graphics.opengl";
 }
 }
 
 
-love::graphics::StreamBuffer *Graphics::newStreamBuffer(BufferType type, size_t size)
+love::graphics::StreamBuffer *Graphics::newStreamBuffer(BufferUsage type, size_t size)
 {
 {
 	return CreateStreamBuffer(type, size);
 	return CreateStreamBuffer(type, size);
 }
 }
@@ -321,6 +321,9 @@ bool Graphics::setMode(void */*context*/, int width, int height, int pixelwidth,
 		glEnable(GL_TEXTURE_2D);
 		glEnable(GL_TEXTURE_2D);
 	}
 	}
 
 
+	if (!GLAD_ES_VERSION_2_0)
+		glEnable(GL_VERTEX_PROGRAM_POINT_SIZE);
+
 	gl.setTextureUnit(0);
 	gl.setTextureUnit(0);
 
 
 	// Set pixel row alignment - code that calls glTexSubImage and glReadPixels
 	// Set pixel row alignment - code that calls glTexSubImage and glReadPixels
@@ -350,46 +353,46 @@ bool Graphics::setMode(void */*context*/, int width, int height, int pixelwidth,
 	{
 	{
 		// Initial sizes that should be good enough for most cases. It will
 		// Initial sizes that should be good enough for most cases. It will
 		// resize to fit if needed, later.
 		// resize to fit if needed, later.
-		batchedDrawState.vb[0] = CreateStreamBuffer(BUFFERTYPE_VERTEX, 1024 * 1024 * 1);
-		batchedDrawState.vb[1] = CreateStreamBuffer(BUFFERTYPE_VERTEX, 256  * 1024 * 1);
-		batchedDrawState.indexBuffer = CreateStreamBuffer(BUFFERTYPE_INDEX, sizeof(uint16) * LOVE_UINT16_MAX);
+		batchedDrawState.vb[0] = CreateStreamBuffer(BUFFERUSAGE_VERTEX, 1024 * 1024 * 1);
+		batchedDrawState.vb[1] = CreateStreamBuffer(BUFFERUSAGE_VERTEX, 256  * 1024 * 1);
+		batchedDrawState.indexBuffer = CreateStreamBuffer(BUFFERUSAGE_INDEX, sizeof(uint16) * LOVE_UINT16_MAX);
 	}
 	}
 
 
-	if (capabilities.features[FEATURE_TEXEL_BUFFER] && defaultBuffers[BUFFERTYPE_TEXEL].get() == nullptr)
+	if (capabilities.features[FEATURE_TEXEL_BUFFER] && defaultBuffers[BUFFERUSAGE_TEXEL].get() == nullptr)
 	{
 	{
-		Buffer::Settings settings(Buffer::TYPEFLAG_TEXEL, BUFFERUSAGE_STATIC);
+		Buffer::Settings settings(BUFFERUSAGEFLAG_TEXEL, BUFFERDATAUSAGE_STATIC);
 		std::vector<Buffer::DataDeclaration> format = {{"", DATAFORMAT_FLOAT_VEC4, 0}};
 		std::vector<Buffer::DataDeclaration> format = {{"", DATAFORMAT_FLOAT_VEC4, 0}};
 
 
 		const float texel[] = {0.0f, 0.0f, 0.0f, 1.0f};
 		const float texel[] = {0.0f, 0.0f, 0.0f, 1.0f};
 
 
 		auto buffer = newBuffer(settings, format, texel, sizeof(texel), 1);
 		auto buffer = newBuffer(settings, format, texel, sizeof(texel), 1);
-		defaultBuffers[BUFFERTYPE_TEXEL].set(buffer, Acquire::NORETAIN);
+		defaultBuffers[BUFFERUSAGE_TEXEL].set(buffer, Acquire::NORETAIN);
 	}
 	}
 
 
-	if (capabilities.features[FEATURE_GLSL4] && defaultBuffers[BUFFERTYPE_SHADER_STORAGE].get() == nullptr)
+	if (capabilities.features[FEATURE_GLSL4] && defaultBuffers[BUFFERUSAGE_SHADER_STORAGE].get() == nullptr)
 	{
 	{
-		Buffer::Settings settings(Buffer::TYPEFLAG_SHADER_STORAGE, BUFFERUSAGE_STATIC);
+		Buffer::Settings settings(BUFFERUSAGEFLAG_SHADER_STORAGE, BUFFERDATAUSAGE_STATIC);
 		std::vector<Buffer::DataDeclaration> format = {{"", DATAFORMAT_FLOAT, 0}};
 		std::vector<Buffer::DataDeclaration> format = {{"", DATAFORMAT_FLOAT, 0}};
 
 
 		std::vector<float> data;
 		std::vector<float> data;
 		data.resize(Buffer::SHADER_STORAGE_BUFFER_MAX_STRIDE / 4);
 		data.resize(Buffer::SHADER_STORAGE_BUFFER_MAX_STRIDE / 4);
 
 
 		auto buffer = newBuffer(settings, format, data.data(), data.size() * sizeof(float), data.size());
 		auto buffer = newBuffer(settings, format, data.data(), data.size() * sizeof(float), data.size());
-		defaultBuffers[BUFFERTYPE_TEXEL].set(buffer, Acquire::NORETAIN);
+		defaultBuffers[BUFFERUSAGE_TEXEL].set(buffer, Acquire::NORETAIN);
 	}
 	}
 
 
 	// Load default resources before other Volatile.
 	// Load default resources before other Volatile.
-	for (int i = 0; i < BUFFERTYPE_MAX_ENUM; i++)
+	for (int i = 0; i < BUFFERUSAGE_MAX_ENUM; i++)
 	{
 	{
 		if (defaultBuffers[i].get())
 		if (defaultBuffers[i].get())
 			((Buffer *) defaultBuffers[i].get())->loadVolatile();
 			((Buffer *) defaultBuffers[i].get())->loadVolatile();
 	}
 	}
 
 
-	if (defaultBuffers[BUFFERTYPE_TEXEL].get())
-		gl.setDefaultTexelBuffer((GLuint) defaultBuffers[BUFFERTYPE_TEXEL]->getTexelBufferHandle());
+	if (defaultBuffers[BUFFERUSAGE_TEXEL].get())
+		gl.setDefaultTexelBuffer((GLuint) defaultBuffers[BUFFERUSAGE_TEXEL]->getTexelBufferHandle());
 
 
-	if (defaultBuffers[BUFFERTYPE_SHADER_STORAGE].get())
-		gl.setDefaultStorageBuffer((GLuint) defaultBuffers[BUFFERTYPE_SHADER_STORAGE]->getHandle());
+	if (defaultBuffers[BUFFERUSAGE_SHADER_STORAGE].get())
+		gl.setDefaultStorageBuffer((GLuint) defaultBuffers[BUFFERUSAGE_SHADER_STORAGE]->getHandle());
 
 
 	// Reload all volatile objects.
 	// Reload all volatile objects.
 	if (!Volatile::loadAll())
 	if (!Volatile::loadAll())
@@ -511,7 +514,7 @@ void Graphics::draw(const DrawIndexedCommand &cmd)
 	GLenum glprimitivetype = OpenGL::getGLPrimitiveType(cmd.primitiveType);
 	GLenum glprimitivetype = OpenGL::getGLPrimitiveType(cmd.primitiveType);
 	GLenum gldatatype = OpenGL::getGLIndexDataType(cmd.indexType);
 	GLenum gldatatype = OpenGL::getGLIndexDataType(cmd.indexType);
 
 
-	gl.bindBuffer(BUFFERTYPE_INDEX, cmd.indexBuffer->getHandle());
+	gl.bindBuffer(BUFFERUSAGE_INDEX, cmd.indexBuffer->getHandle());
 
 
 	if (cmd.instanceCount > 1)
 	if (cmd.instanceCount > 1)
 		glDrawElementsInstanced(glprimitivetype, cmd.indexCount, gldatatype, gloffset, cmd.instanceCount);
 		glDrawElementsInstanced(glprimitivetype, cmd.indexCount, gldatatype, gloffset, cmd.instanceCount);
@@ -553,7 +556,7 @@ void Graphics::drawQuads(int start, int count, const VertexAttributes &attribute
 	gl.bindTextureToUnit(texture, 0, false);
 	gl.bindTextureToUnit(texture, 0, false);
 	gl.setCullMode(CULL_NONE);
 	gl.setCullMode(CULL_NONE);
 
 
-	gl.bindBuffer(BUFFERTYPE_INDEX, quadIndexBuffer->getHandle());
+	gl.bindBuffer(BUFFERUSAGE_INDEX, quadIndexBuffer->getHandle());
 
 
 	if (gl.isBaseVertexSupported())
 	if (gl.isBaseVertexSupported())
 	{
 	{
@@ -1462,10 +1465,9 @@ void Graphics::setBlendState(const BlendState &blend)
 
 
 void Graphics::setPointSize(float size)
 void Graphics::setPointSize(float size)
 {
 {
-	if (batchedDrawState.primitiveMode == PRIMITIVE_POINTS)
+	if (size != states.back().pointSize)
 		flushBatchedDraws();
 		flushBatchedDraws();
 
 
-	gl.setPointSize(size * getCurrentDPIScale());
 	states.back().pointSize = size;
 	states.back().pointSize = size;
 }
 }
 
 
@@ -1553,8 +1555,9 @@ void Graphics::initCapabilities()
 	capabilities.features[FEATURE_GLSL3] = GLAD_ES_VERSION_3_0 || gl.isCoreProfile();
 	capabilities.features[FEATURE_GLSL3] = GLAD_ES_VERSION_3_0 || gl.isCoreProfile();
 	capabilities.features[FEATURE_GLSL4] = GLAD_ES_VERSION_3_1 || (gl.isCoreProfile() && GLAD_VERSION_4_3);
 	capabilities.features[FEATURE_GLSL4] = GLAD_ES_VERSION_3_1 || (gl.isCoreProfile() && GLAD_VERSION_4_3);
 	capabilities.features[FEATURE_INSTANCING] = gl.isInstancingSupported();
 	capabilities.features[FEATURE_INSTANCING] = gl.isInstancingSupported();
-	capabilities.features[FEATURE_TEXEL_BUFFER] = gl.isBufferTypeSupported(BUFFERTYPE_TEXEL);
-	static_assert(FEATURE_MAX_ENUM == 11, "Graphics::initCapabilities must be updated when adding a new graphics feature!");
+	capabilities.features[FEATURE_TEXEL_BUFFER] = gl.isBufferUsageSupported(BUFFERUSAGE_TEXEL);
+	capabilities.features[FEATURE_COPY_BUFFER] = gl.isBufferUsageSupported(BUFFERUSAGE_COPY_SOURCE);
+	static_assert(FEATURE_MAX_ENUM == 12, "Graphics::initCapabilities must be updated when adding a new graphics feature!");
 
 
 	capabilities.limits[LIMIT_POINT_SIZE] = gl.getMaxPointSize();
 	capabilities.limits[LIMIT_POINT_SIZE] = gl.getMaxPointSize();
 	capabilities.limits[LIMIT_TEXTURE_SIZE] = gl.getMax2DTextureSize();
 	capabilities.limits[LIMIT_TEXTURE_SIZE] = gl.getMax2DTextureSize();
@@ -1572,14 +1575,20 @@ void Graphics::initCapabilities()
 		capabilities.textureTypes[i] = gl.isTextureTypeSupported((TextureType) i);
 		capabilities.textureTypes[i] = gl.isTextureTypeSupported((TextureType) i);
 }
 }
 
 
-PixelFormat Graphics::getSizedFormat(PixelFormat format, bool rendertarget, bool readable, bool sRGB) const
+PixelFormat Graphics::getSizedFormat(PixelFormat format, bool rendertarget, bool readable) const
 {
 {
+	uint32 requiredflags = 0;
+	if (rendertarget)
+		requiredflags |= PIXELFORMATUSAGEFLAGS_RENDERTARGET;
+	if (readable)
+		requiredflags |= PIXELFORMATUSAGEFLAGS_SAMPLE;
+
 	switch (format)
 	switch (format)
 	{
 	{
 	case PIXELFORMAT_NORMAL:
 	case PIXELFORMAT_NORMAL:
 		if (isGammaCorrect())
 		if (isGammaCorrect())
 			return PIXELFORMAT_RGBA8_UNORM_sRGB;
 			return PIXELFORMAT_RGBA8_UNORM_sRGB;
-		else if (!OpenGL::isPixelFormatSupported(PIXELFORMAT_RGBA8_UNORM, rendertarget, readable, sRGB))
+		else if ((OpenGL::getPixelFormatUsageFlags(PIXELFORMAT_RGBA8_UNORM) & requiredflags) != requiredflags)
 			// 32-bit render targets don't have guaranteed support on GLES2.
 			// 32-bit render targets don't have guaranteed support on GLES2.
 			return PIXELFORMAT_RGBA4_UNORM;
 			return PIXELFORMAT_RGBA4_UNORM;
 		else
 		else
@@ -1595,18 +1604,26 @@ bool Graphics::isPixelFormatSupported(PixelFormat format, bool rendertarget, boo
 {
 {
 	if (sRGB && format == PIXELFORMAT_RGBA8_UNORM)
 	if (sRGB && format == PIXELFORMAT_RGBA8_UNORM)
 	{
 	{
-		format = PIXELFORMAT_RGBA8_UNORM_sRGB;
+		format = getSRGBPixelFormat(format);
 		sRGB = false;
 		sRGB = false;
 	}
 	}
 
 
-	format = getSizedFormat(format, rendertarget, readable, sRGB);
+	uint32 requiredflags = 0;
+	if (rendertarget)
+		requiredflags |= PIXELFORMATUSAGEFLAGS_RENDERTARGET;
+	if (readable)
+		requiredflags |= PIXELFORMATUSAGEFLAGS_SAMPLE;
+
+	format = getSizedFormat(format, rendertarget, readable);
 
 
 	OptionalBool &supported = supportedFormats[format][rendertarget ? 1 : 0][readable ? 1 : 0][sRGB ? 1 : 0];
 	OptionalBool &supported = supportedFormats[format][rendertarget ? 1 : 0][readable ? 1 : 0][sRGB ? 1 : 0];
 
 
 	if (supported.hasValue)
 	if (supported.hasValue)
 		return supported.value;
 		return supported.value;
 
 
-	if (!OpenGL::isPixelFormatSupported(format, rendertarget, readable, sRGB))
+	auto supportedflags = OpenGL::getPixelFormatUsageFlags(format);
+
+	if ((requiredflags & supportedflags) != requiredflags)
 	{
 	{
 		supported.set(false);
 		supported.set(false);
 		return supported.value;
 		return supported.value;
@@ -1636,7 +1653,7 @@ bool Graphics::isPixelFormatSupported(PixelFormat format, bool rendertarget, boo
 		return true;
 		return true;
 	}
 	}
 
 
-	OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(format, readable, sRGB);
+	OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(format, !readable, sRGB);
 
 
 	GLuint current_fbo = gl.getFramebuffer(OpenGL::FRAMEBUFFER_ALL);
 	GLuint current_fbo = gl.getFramebuffer(OpenGL::FRAMEBUFFER_ALL);
 
 

+ 3 - 3
src/modules/graphics/opengl/Graphics.h

@@ -104,7 +104,7 @@ public:
 
 
 	void setWireframe(bool enable) override;
 	void setWireframe(bool enable) override;
 
 
-	PixelFormat getSizedFormat(PixelFormat format, bool rendertarget, bool readable, bool sRGB) const override;
+	PixelFormat getSizedFormat(PixelFormat format, bool rendertarget, bool readable) const override;
 	bool isPixelFormatSupported(PixelFormat format, bool rendertarget, bool readable, bool sRGB = false) override;
 	bool isPixelFormatSupported(PixelFormat format, bool rendertarget, bool readable, bool sRGB = false) override;
 	Renderer getRenderer() const override;
 	Renderer getRenderer() const override;
 	bool usesGLSLES() const override;
 	bool usesGLSLES() const override;
@@ -139,7 +139,7 @@ private:
 
 
 	love::graphics::ShaderStage *newShaderStageInternal(ShaderStage::StageType stage, const std::string &cachekey, const std::string &source, bool gles) override;
 	love::graphics::ShaderStage *newShaderStageInternal(ShaderStage::StageType stage, const std::string &cachekey, const std::string &source, bool gles) override;
 	love::graphics::Shader *newShaderInternal(love::graphics::ShaderStage *vertex, love::graphics::ShaderStage *pixel) override;
 	love::graphics::Shader *newShaderInternal(love::graphics::ShaderStage *vertex, love::graphics::ShaderStage *pixel) override;
-	love::graphics::StreamBuffer *newStreamBuffer(BufferType type, size_t size) override;
+	love::graphics::StreamBuffer *newStreamBuffer(BufferUsage type, size_t size) override;
 	void setRenderTargetsInternal(const RenderTargets &rts, int w, int h, int pixelw, int pixelh, bool hasSRGBtexture) override;
 	void setRenderTargetsInternal(const RenderTargets &rts, int w, int h, int pixelw, int pixelh, bool hasSRGBtexture) override;
 	void initCapabilities() override;
 	void initCapabilities() override;
 	void getAPIStats(int &shaderswitches) const override;
 	void getAPIStats(int &shaderswitches) const override;
@@ -167,7 +167,7 @@ private:
 	size_t bufferMapMemorySize;
 	size_t bufferMapMemorySize;
 
 
 	// Only needed for buffer types that can be bound to shaders.
 	// Only needed for buffer types that can be bound to shaders.
-	StrongRef<love::graphics::Buffer> defaultBuffers[BUFFERTYPE_MAX_ENUM];
+	StrongRef<love::graphics::Buffer> defaultBuffers[BUFFERUSAGE_MAX_ENUM];
 
 
 	// [rendertarget][readable][srgb]
 	// [rendertarget][readable][srgb]
 	OptionalBool supportedFormats[PIXELFORMAT_MAX_ENUM][2][2][2];
 	OptionalBool supportedFormats[PIXELFORMAT_MAX_ENUM][2][2][2];

+ 162 - 152
src/modules/graphics/opengl/OpenGL.cpp

@@ -235,15 +235,15 @@ void OpenGL::setupContext()
 	glGetIntegerv(GL_CULL_FACE_MODE, &faceCull);
 	glGetIntegerv(GL_CULL_FACE_MODE, &faceCull);
 	state.faceCullMode = faceCull;
 	state.faceCullMode = faceCull;
 
 
-	for (int i = 0; i < (int) BUFFERTYPE_MAX_ENUM; i++)
+	for (int i = 0; i < (int) BUFFERUSAGE_MAX_ENUM; i++)
 	{
 	{
 		state.boundBuffers[i] = 0;
 		state.boundBuffers[i] = 0;
-		if (isBufferTypeSupported((BufferType) i))
-			glBindBuffer(getGLBufferType((BufferType) i), 0);
+		if (isBufferUsageSupported((BufferUsage) i))
+			glBindBuffer(getGLBufferType((BufferUsage) i), 0);
 	}
 	}
 
 
-	if (isBufferTypeSupported(BUFFERTYPE_SHADER_STORAGE))
-		state.boundIndexedBuffers[BUFFERTYPE_SHADER_STORAGE].resize(maxShaderStorageBufferBindings, 0);
+	if (isBufferUsageSupported(BUFFERUSAGE_SHADER_STORAGE))
+		state.boundIndexedBuffers[BUFFERUSAGE_SHADER_STORAGE].resize(maxShaderStorageBufferBindings, 0);
 
 
 	// Initialize multiple texture unit support for shaders.
 	// Initialize multiple texture unit support for shaders.
 	for (int i = 0; i < TEXTURE_MAX_ENUM + 1; i++)
 	for (int i = 0; i < TEXTURE_MAX_ENUM + 1; i++)
@@ -484,12 +484,12 @@ void OpenGL::initMaxValues()
 	else
 	else
 		maxTextureArrayLayers = 0;
 		maxTextureArrayLayers = 0;
 
 
-	if (isBufferTypeSupported(BUFFERTYPE_TEXEL))
+	if (isBufferUsageSupported(BUFFERUSAGE_TEXEL))
 		glGetIntegerv(GL_MAX_TEXTURE_BUFFER_SIZE, &maxTexelBufferSize);
 		glGetIntegerv(GL_MAX_TEXTURE_BUFFER_SIZE, &maxTexelBufferSize);
 	else
 	else
 		maxTexelBufferSize = 0;
 		maxTexelBufferSize = 0;
 
 
-	if (isBufferTypeSupported(BUFFERTYPE_SHADER_STORAGE))
+	if (isBufferUsageSupported(BUFFERUSAGE_SHADER_STORAGE))
 	{
 	{
 		glGetIntegerv(GL_MAX_SHADER_STORAGE_BLOCK_SIZE, &maxShaderStorageBufferSize);
 		glGetIntegerv(GL_MAX_SHADER_STORAGE_BLOCK_SIZE, &maxShaderStorageBufferSize);
 		glGetIntegerv(GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS, &maxShaderStorageBufferBindings);
 		glGetIntegerv(GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS, &maxShaderStorageBufferBindings);
@@ -613,16 +613,18 @@ GLenum OpenGL::getGLPrimitiveType(PrimitiveType type)
 	return GL_ZERO;
 	return GL_ZERO;
 }
 }
 
 
-GLenum OpenGL::getGLBufferType(BufferType type)
+GLenum OpenGL::getGLBufferType(BufferUsage usage)
 {
 {
-	switch (type)
+	switch (usage)
 	{
 	{
-		case BUFFERTYPE_VERTEX: return GL_ARRAY_BUFFER;
-		case BUFFERTYPE_INDEX: return GL_ELEMENT_ARRAY_BUFFER;
-		case BUFFERTYPE_TEXEL: return GL_TEXTURE_BUFFER;
-		case BUFFERTYPE_UNIFORM: return GL_UNIFORM_BUFFER;
-		case BUFFERTYPE_SHADER_STORAGE: return GL_SHADER_STORAGE_BUFFER;
-		case BUFFERTYPE_MAX_ENUM: return GL_ZERO;
+		case BUFFERUSAGE_VERTEX: return GL_ARRAY_BUFFER;
+		case BUFFERUSAGE_INDEX: return GL_ELEMENT_ARRAY_BUFFER;
+		case BUFFERUSAGE_UNIFORM: return GL_UNIFORM_BUFFER;
+		case BUFFERUSAGE_TEXEL: return GL_TEXTURE_BUFFER;
+		case BUFFERUSAGE_SHADER_STORAGE: return GL_SHADER_STORAGE_BUFFER;
+		case BUFFERUSAGE_COPY_SOURCE: return GL_COPY_READ_BUFFER;
+		case BUFFERUSAGE_COPY_DEST: return GL_COPY_WRITE_BUFFER;
+		case BUFFERUSAGE_MAX_ENUM: return GL_ZERO;
 	}
 	}
 
 
 	return GL_ZERO;
 	return GL_ZERO;
@@ -791,18 +793,18 @@ GLenum OpenGL::getGLVertexDataType(DataFormat format, int &components, GLboolean
 	return GL_ZERO;
 	return GL_ZERO;
 }
 }
 
 
-GLenum OpenGL::getGLBufferUsage(BufferUsage usage)
+GLenum OpenGL::getGLBufferDataUsage(BufferDataUsage usage)
 {
 {
 	switch (usage)
 	switch (usage)
 	{
 	{
-		case BUFFERUSAGE_STREAM: return GL_STREAM_DRAW;
-		case BUFFERUSAGE_DYNAMIC: return GL_DYNAMIC_DRAW;
-		case BUFFERUSAGE_STATIC: return GL_STATIC_DRAW;
+		case BUFFERDATAUSAGE_STREAM: return GL_STREAM_DRAW;
+		case BUFFERDATAUSAGE_DYNAMIC: return GL_DYNAMIC_DRAW;
+		case BUFFERDATAUSAGE_STATIC: return GL_STATIC_DRAW;
 		default: return 0;
 		default: return 0;
 	}
 	}
 }
 }
 
 
-void OpenGL::bindBuffer(BufferType type, GLuint buffer)
+void OpenGL::bindBuffer(BufferUsage type, GLuint buffer)
 {
 {
 	if (state.boundBuffers[type] != buffer)
 	if (state.boundBuffers[type] != buffer)
 	{
 	{
@@ -815,7 +817,7 @@ void OpenGL::deleteBuffer(GLuint buffer)
 {
 {
 	glDeleteBuffers(1, &buffer);
 	glDeleteBuffers(1, &buffer);
 
 
-	for (int i = 0; i < (int) BUFFERTYPE_MAX_ENUM; i++)
+	for (int i = 0; i < (int) BUFFERUSAGE_MAX_ENUM; i++)
 	{
 	{
 		if (state.boundBuffers[i] == buffer)
 		if (state.boundBuffers[i] == buffer)
 			state.boundBuffers[i] = 0;
 			state.boundBuffers[i] = 0;
@@ -868,7 +870,7 @@ void OpenGL::setVertexAttributes(const VertexAttributes &attributes, const Buffe
 
 
 			const void *offsetpointer = reinterpret_cast<void*>(bufferinfo.offset + attrib.offsetFromVertex);
 			const void *offsetpointer = reinterpret_cast<void*>(bufferinfo.offset + attrib.offsetFromVertex);
 
 
-			bindBuffer(BUFFERTYPE_VERTEX, (GLuint) bufferinfo.buffer->getHandle());
+			bindBuffer(BUFFERUSAGE_VERTEX, (GLuint) bufferinfo.buffer->getHandle());
 
 
 			if (intformat)
 			if (intformat)
 				glVertexAttribIPointer(i, components, gltype, layout.stride, offsetpointer);
 				glVertexAttribIPointer(i, components, gltype, layout.stride, offsetpointer);
@@ -942,19 +944,6 @@ void OpenGL::setScissor(const Rect &v, bool rtActive)
 	state.scissor = v;
 	state.scissor = v;
 }
 }
 
 
-void OpenGL::setPointSize(float size)
-{
-	if (GLAD_VERSION_1_0)
-		glPointSize(size);
-
-	state.pointSize = size;
-}
-
-float OpenGL::getPointSize() const
-{
-	return state.pointSize;
-}
-
 void OpenGL::setEnableState(EnableState enablestate, bool enable)
 void OpenGL::setEnableState(EnableState enablestate, bool enable)
 {
 {
 	GLenum glstate = GL_NONE;
 	GLenum glstate = GL_NONE;
@@ -1164,7 +1153,7 @@ void OpenGL::bindTextureToUnit(Texture *texture, int textureunit, bool restorepr
 	bindTextureToUnit(textype, handle, textureunit, restoreprev, bindforedit);
 	bindTextureToUnit(textype, handle, textureunit, restoreprev, bindforedit);
 }
 }
 
 
-void OpenGL::bindIndexedBuffer(GLuint buffer, BufferType type, int index)
+void OpenGL::bindIndexedBuffer(GLuint buffer, BufferUsage type, int index)
 {
 {
 	auto &bindings = state.boundIndexedBuffers[type];
 	auto &bindings = state.boundIndexedBuffers[type];
 	if (bindings.size() > (size_t) index && buffer != bindings[index])
 	if (bindings.size() > (size_t) index && buffer != bindings[index])
@@ -1433,19 +1422,22 @@ bool OpenGL::isTextureTypeSupported(TextureType type) const
 	return false;
 	return false;
 }
 }
 
 
-bool OpenGL::isBufferTypeSupported(BufferType type) const
+bool OpenGL::isBufferUsageSupported(BufferUsage usage) const
 {
 {
-	switch (type)
+	switch (usage)
 	{
 	{
-	case BUFFERTYPE_VERTEX:
-	case BUFFERTYPE_INDEX:
+	case BUFFERUSAGE_VERTEX:
+	case BUFFERUSAGE_INDEX:
 		return true;
 		return true;
-	case BUFFERTYPE_TEXEL:
+	case BUFFERUSAGE_TEXEL:
 		// Not supported in ES until 3.2, which we don't support shaders for...
 		// Not supported in ES until 3.2, which we don't support shaders for...
 		return GLAD_VERSION_3_1;
 		return GLAD_VERSION_3_1;
-	case BUFFERTYPE_SHADER_STORAGE:
+	case BUFFERUSAGE_SHADER_STORAGE:
 		return (GLAD_VERSION_4_3 && isCoreProfile()) || GLAD_ES_VERSION_3_1;
 		return (GLAD_VERSION_4_3 && isCoreProfile()) || GLAD_ES_VERSION_3_1;
-	case BUFFERTYPE_MAX_ENUM:
+	case BUFFERUSAGE_COPY_SOURCE:
+	case BUFFERUSAGE_COPY_DEST:
+		return GLAD_VERSION_3_1 || GLAD_ES_VERSION_3_0;
+	case BUFFERUSAGE_MAX_ENUM:
 		return false;
 		return false;
 	}
 	}
 	return false;
 	return false;
@@ -1933,152 +1925,178 @@ OpenGL::TextureFormat OpenGL::convertPixelFormat(PixelFormat pixelformat, bool r
 	return f;
 	return f;
 }
 }
 
 
-bool OpenGL::isPixelFormatSupported(PixelFormat pixelformat, bool rendertarget, bool readable, bool isSRGB)
+uint32 OpenGL::getPixelFormatUsageFlags(PixelFormat pixelformat)
 {
 {
-	if (rendertarget && isPixelFormatCompressed(pixelformat))
-		return false;
+	const uint32 commonsample = PIXELFORMATUSAGEFLAGS_SAMPLE | PIXELFORMATUSAGEFLAGS_LINEAR;
+	const uint32 commonrender = PIXELFORMATUSAGEFLAGS_RENDERTARGET | PIXELFORMATUSAGEFLAGS_BLEND | PIXELFORMATUSAGEFLAGS_MSAA;
 
 
-	if (isSRGB)
-		pixelformat = getSRGBPixelFormat(pixelformat);
+	uint32 flags = PIXELFORMATUSAGEFLAGS_NONE;
 
 
 	switch (pixelformat)
 	switch (pixelformat)
 	{
 	{
 	case PIXELFORMAT_R8_UNORM:
 	case PIXELFORMAT_R8_UNORM:
 	case PIXELFORMAT_RG8_UNORM:
 	case PIXELFORMAT_RG8_UNORM:
 		if (GLAD_VERSION_3_0 || GLAD_ES_VERSION_3_0 || GLAD_ARB_texture_rg || GLAD_EXT_texture_rg)
 		if (GLAD_VERSION_3_0 || GLAD_ES_VERSION_3_0 || GLAD_ARB_texture_rg || GLAD_EXT_texture_rg)
-			return true;
-		else if (pixelformat == PIXELFORMAT_R8_UNORM && !rendertarget && (GLAD_ES_VERSION_2_0 || GLAD_VERSION_1_1))
-			return true; // We'll use OpenGL's luminance format internally.
-		else
-			return false;
+			flags |= commonsample | commonrender;
+		else if (pixelformat == PIXELFORMAT_R8_UNORM && (GLAD_ES_VERSION_2_0 || GLAD_VERSION_1_1))
+			flags |= commonsample; // We'll use OpenGL's luminance format internally.
+		break;
 	case PIXELFORMAT_RGBA8_UNORM:
 	case PIXELFORMAT_RGBA8_UNORM:
-		if (rendertarget)
-			return GLAD_VERSION_1_0 || GLAD_ES_VERSION_3_0 || GLAD_OES_rgb8_rgba8 || GLAD_ARM_rgba8;
-		else
-			return true;
+		flags |= commonsample;
+		if (GLAD_VERSION_1_0 || GLAD_ES_VERSION_3_0 || GLAD_OES_rgb8_rgba8 || GLAD_ARM_rgba8)
+			flags |= commonrender;
+		break;
 	case PIXELFORMAT_RGBA8_UNORM_sRGB:
 	case PIXELFORMAT_RGBA8_UNORM_sRGB:
 		if (gl.bugs.brokenSRGB)
 		if (gl.bugs.brokenSRGB)
-			return false;
-		if (rendertarget)
-		{
-			if (GLAD_VERSION_1_0)
-			{
-				return GLAD_VERSION_3_0 || ((GLAD_ARB_framebuffer_sRGB || GLAD_EXT_framebuffer_sRGB)
-					   && (GLAD_VERSION_2_1 || GLAD_EXT_texture_sRGB));
-			}
-			else
-				return GLAD_ES_VERSION_3_0;
-		}
-		else
-			return GLAD_ES_VERSION_3_0 || GLAD_VERSION_2_1 || GLAD_EXT_texture_sRGB;
+			break;
+		if (GLAD_ES_VERSION_3_0 || GLAD_VERSION_2_1 || GLAD_EXT_texture_sRGB)
+			flags |= commonsample;
+		if (GLAD_ES_VERSION_3_0 || GLAD_VERSION_3_0
+			|| ((GLAD_ARB_framebuffer_sRGB || GLAD_EXT_framebuffer_sRGB) && (GLAD_VERSION_2_1 || GLAD_EXT_texture_sRGB)))
+			flags |= commonrender;
+		break;
 	case PIXELFORMAT_R16_UNORM:
 	case PIXELFORMAT_R16_UNORM:
 	case PIXELFORMAT_RG16_UNORM:
 	case PIXELFORMAT_RG16_UNORM:
-		return GLAD_VERSION_3_0
+		if (GLAD_VERSION_3_0
 			|| (GLAD_VERSION_1_1 && GLAD_ARB_texture_rg)
 			|| (GLAD_VERSION_1_1 && GLAD_ARB_texture_rg)
-			|| (GLAD_EXT_texture_norm16 && (GLAD_ES_VERSION_3_0 || GLAD_EXT_texture_rg));
+			|| (GLAD_EXT_texture_norm16 && (GLAD_ES_VERSION_3_0 || GLAD_EXT_texture_rg)))
+			flags |= commonsample | commonrender;
+		break;
 	case PIXELFORMAT_RGBA16_UNORM:
 	case PIXELFORMAT_RGBA16_UNORM:
-		return GLAD_VERSION_1_1 || GLAD_EXT_texture_norm16;
+		if (GLAD_VERSION_1_1 || GLAD_EXT_texture_norm16)
+			flags |= commonsample | commonrender;
+		break;
 	case PIXELFORMAT_R16_FLOAT:
 	case PIXELFORMAT_R16_FLOAT:
 	case PIXELFORMAT_RG16_FLOAT:
 	case PIXELFORMAT_RG16_FLOAT:
-		if (GLAD_VERSION_1_0)
-			return GLAD_VERSION_3_0 || (GLAD_ARB_texture_float && GLAD_ARB_half_float_pixel && GLAD_ARB_texture_rg);
-		else if (rendertarget && !GLAD_EXT_color_buffer_half_float)
-			return false;
-		else
-			return GLAD_ES_VERSION_3_0 || (GLAD_OES_texture_half_float && GLAD_EXT_texture_rg);
+		if (GLAD_VERSION_1_0 && (GLAD_VERSION_3_0 || (GLAD_ARB_texture_float && GLAD_ARB_half_float_pixel && GLAD_ARB_texture_rg)))
+			flags |= commonsample | commonrender;
+		if (GLAD_ES_VERSION_3_0 || (GLAD_OES_texture_half_float && GLAD_EXT_texture_rg))
+			flags |= commonsample;
+		if (GLAD_EXT_color_buffer_half_float && (GLAD_ES_VERSION_3_0 || GLAD_EXT_texture_rg))
+			flags |= commonrender;
+		if (!(GLAD_VERSION_1_1 || GLAD_ES_VERSION_3_0 || GLAD_OES_texture_half_float_linear))
+			flags &= ~PIXELFORMATUSAGEFLAGS_LINEAR;
+		break;
 	case PIXELFORMAT_RGBA16_FLOAT:
 	case PIXELFORMAT_RGBA16_FLOAT:
-		if (GLAD_VERSION_1_0)
-			return GLAD_VERSION_3_0 || (GLAD_ARB_texture_float && GLAD_ARB_half_float_pixel);
-		else if (rendertarget && !GLAD_EXT_color_buffer_half_float)
-			return false;
-		else
-			return GLAD_ES_VERSION_3_0 || GLAD_OES_texture_half_float;
+		if (GLAD_VERSION_3_0 || (GLAD_VERSION_1_0 && GLAD_ARB_texture_float && GLAD_ARB_half_float_pixel))
+			flags |= commonsample | commonrender;
+		if (GLAD_ES_VERSION_3_0 || GLAD_OES_texture_half_float)
+			flags |= commonsample;
+		if (GLAD_EXT_color_buffer_half_float)
+			flags |= commonrender;
+		if (!(GLAD_VERSION_1_1 || GLAD_ES_VERSION_3_0 || GLAD_OES_texture_half_float_linear))
+			flags &= ~PIXELFORMATUSAGEFLAGS_LINEAR;
+		break;
 	case PIXELFORMAT_R32_FLOAT:
 	case PIXELFORMAT_R32_FLOAT:
 	case PIXELFORMAT_RG32_FLOAT:
 	case PIXELFORMAT_RG32_FLOAT:
-		if (GLAD_VERSION_1_0)
-			return GLAD_VERSION_3_0 || (GLAD_ARB_texture_float && GLAD_ARB_texture_rg);
-		else if (!rendertarget)
-			return GLAD_ES_VERSION_3_0 || (GLAD_OES_texture_float && GLAD_EXT_texture_rg);
-		else
-			return false;
+		if (GLAD_VERSION_3_0 || (GLAD_VERSION_1_0 && GLAD_ARB_texture_float && GLAD_ARB_texture_rg))
+			flags |= commonsample | commonrender;
+		if (GLAD_ES_VERSION_3_0 || (GLAD_OES_texture_float && GLAD_EXT_texture_rg))
+			flags |= commonsample;
+		if (!(GLAD_VERSION_1_1 || GLAD_ES_VERSION_3_0 || GLAD_OES_texture_half_float_linear))
+			flags &= ~PIXELFORMATUSAGEFLAGS_LINEAR;
+		break;
 	case PIXELFORMAT_RGBA32_FLOAT:
 	case PIXELFORMAT_RGBA32_FLOAT:
-		if (GLAD_VERSION_1_0)
-			return GLAD_VERSION_3_0 || GLAD_ARB_texture_float;
-		else if (!rendertarget)
-			return GLAD_ES_VERSION_3_0 || GLAD_OES_texture_float;
-		else
-			return false;
+		if (GLAD_VERSION_3_0 || (GLAD_VERSION_1_0 && GLAD_ARB_texture_float))
+			flags |= commonsample | commonrender;
+		if (GLAD_ES_VERSION_3_0 || GLAD_OES_texture_float)
+			flags |= commonsample;
+		if (!(GLAD_VERSION_1_1 || GLAD_OES_texture_float_linear))
+			flags &= ~PIXELFORMATUSAGEFLAGS_LINEAR;
+		break;
 
 
 	case PIXELFORMAT_LA8_UNORM:
 	case PIXELFORMAT_LA8_UNORM:
-		return !rendertarget;
+		flags |= commonsample;
+		break;
 
 
 	case PIXELFORMAT_RGBA4_UNORM:
 	case PIXELFORMAT_RGBA4_UNORM:
 	case PIXELFORMAT_RGB5A1_UNORM:
 	case PIXELFORMAT_RGB5A1_UNORM:
-		return true;
+		flags |= commonsample | commonrender;
+		break;
 	case PIXELFORMAT_RGB565_UNORM:
 	case PIXELFORMAT_RGB565_UNORM:
-		return GLAD_ES_VERSION_2_0 || GLAD_VERSION_4_2 || GLAD_ARB_ES2_compatibility;
+		if (GLAD_ES_VERSION_2_0 || GLAD_VERSION_4_2 || GLAD_ARB_ES2_compatibility)
+			flags |= commonsample | commonrender;
+		break;
 	case PIXELFORMAT_RGB10A2_UNORM:
 	case PIXELFORMAT_RGB10A2_UNORM:
-		return GLAD_ES_VERSION_3_0 || GLAD_VERSION_1_0;
+		if (GLAD_ES_VERSION_3_0 || GLAD_VERSION_1_0)
+			flags |= commonsample | commonrender;
+		break;
 	case PIXELFORMAT_RG11B10_FLOAT:
 	case PIXELFORMAT_RG11B10_FLOAT:
-		if (rendertarget)
-			return GLAD_VERSION_3_0 || GLAD_EXT_packed_float || GLAD_APPLE_color_buffer_packed_float;
-		else
-			return GLAD_VERSION_3_0 || GLAD_EXT_packed_float || GLAD_APPLE_texture_packed_float;
+		if (GLAD_VERSION_3_0 || GLAD_EXT_packed_float || GLAD_APPLE_texture_packed_float)
+			flags |= commonsample;
+		if (GLAD_VERSION_3_0 || GLAD_EXT_packed_float || GLAD_APPLE_color_buffer_packed_float)
+			flags |= commonrender;
+		break;
 
 
 	case PIXELFORMAT_STENCIL8:
 	case PIXELFORMAT_STENCIL8:
-		return rendertarget && !readable;
+		flags |= PIXELFORMATUSAGEFLAGS_RENDERTARGET | PIXELFORMATUSAGEFLAGS_MSAA;
+		break;
 
 
 	case PIXELFORMAT_DEPTH16_UNORM:
 	case PIXELFORMAT_DEPTH16_UNORM:
-		if (!rendertarget)
-			return false;
-		else if (readable)
-			return GLAD_VERSION_2_0 || GLAD_ES_VERSION_3_0 || GLAD_OES_depth_texture;
-		else
-			return true;
+		flags |= PIXELFORMATUSAGEFLAGS_RENDERTARGET | PIXELFORMATUSAGEFLAGS_MSAA;
+		if (GLAD_VERSION_2_0 || GLAD_ES_VERSION_3_0 || GLAD_OES_depth_texture)
+			flags |= commonsample;
+		break;
 
 
 	case PIXELFORMAT_DEPTH24_UNORM:
 	case PIXELFORMAT_DEPTH24_UNORM:
-		if (!rendertarget)
-			return false;
-		else if (readable)
-			return GLAD_VERSION_2_0 || GLAD_ES_VERSION_3_0 || (GLAD_OES_depth_texture && (GLAD_OES_depth24 || GLAD_OES_depth_texture));
-		else
-			return GLAD_VERSION_2_0 || GLAD_ES_VERSION_3_0 || GLAD_OES_depth24 || GLAD_OES_depth_texture;
+		if (GLAD_VERSION_2_0 || GLAD_ES_VERSION_3_0 || GLAD_OES_depth24 || GLAD_OES_depth_texture)
+			flags |= PIXELFORMATUSAGEFLAGS_RENDERTARGET | PIXELFORMATUSAGEFLAGS_MSAA;
+
+		if (GLAD_VERSION_2_0 || GLAD_ES_VERSION_3_0 || (GLAD_OES_depth_texture && (GLAD_OES_depth24 || GLAD_OES_depth_texture)))
+			flags |= commonsample;
+		break;
 
 
 	case PIXELFORMAT_DEPTH24_UNORM_STENCIL8:
 	case PIXELFORMAT_DEPTH24_UNORM_STENCIL8:
-		if (!rendertarget)
-			return false;
-		else if (readable)
-			return GLAD_VERSION_3_0 || GLAD_ES_VERSION_3_0 || GLAD_EXT_packed_depth_stencil || (GLAD_OES_depth_texture && GLAD_OES_packed_depth_stencil);
-		else
-			return GLAD_VERSION_3_0 || GLAD_ES_VERSION_3_0 || GLAD_EXT_packed_depth_stencil || GLAD_OES_packed_depth_stencil;
+		if (GLAD_VERSION_3_0 || GLAD_ES_VERSION_3_0 || GLAD_EXT_packed_depth_stencil || GLAD_OES_packed_depth_stencil)
+			flags |= PIXELFORMATUSAGEFLAGS_RENDERTARGET | PIXELFORMATUSAGEFLAGS_MSAA;
+
+		if (GLAD_VERSION_3_0 || GLAD_ES_VERSION_3_0 || GLAD_EXT_packed_depth_stencil || (GLAD_OES_depth_texture && GLAD_OES_packed_depth_stencil))
+			flags |= commonsample;
+		break;
 
 
 	case PIXELFORMAT_DEPTH32_FLOAT:
 	case PIXELFORMAT_DEPTH32_FLOAT:
 	case PIXELFORMAT_DEPTH32_FLOAT_STENCIL8:
 	case PIXELFORMAT_DEPTH32_FLOAT_STENCIL8:
-		return rendertarget && (GLAD_VERSION_3_0 || GLAD_ES_VERSION_3_0 || GLAD_ARB_depth_buffer_float);
+		if (GLAD_VERSION_3_0 || GLAD_ES_VERSION_3_0 || GLAD_ARB_depth_buffer_float)
+			flags |= commonsample | PIXELFORMATUSAGEFLAGS_RENDERTARGET | PIXELFORMATUSAGEFLAGS_MSAA;
+		break;
 
 
 	case PIXELFORMAT_DXT1_UNORM:
 	case PIXELFORMAT_DXT1_UNORM:
-		return GLAD_EXT_texture_compression_s3tc || GLAD_EXT_texture_compression_dxt1;
+		if (GLAD_EXT_texture_compression_s3tc || GLAD_EXT_texture_compression_dxt1)
+			flags |= commonsample;
+		break;
 	case PIXELFORMAT_DXT3_UNORM:
 	case PIXELFORMAT_DXT3_UNORM:
-		return GLAD_EXT_texture_compression_s3tc || GLAD_ANGLE_texture_compression_dxt3;
+		if (GLAD_EXT_texture_compression_s3tc || GLAD_ANGLE_texture_compression_dxt3)
+			flags |= commonsample;
+		break;
 	case PIXELFORMAT_DXT5_UNORM:
 	case PIXELFORMAT_DXT5_UNORM:
-		return GLAD_EXT_texture_compression_s3tc || GLAD_ANGLE_texture_compression_dxt5;
+		if (GLAD_EXT_texture_compression_s3tc || GLAD_ANGLE_texture_compression_dxt5)
+			flags |= commonsample;
+		break;
 	case PIXELFORMAT_BC4_UNORM:
 	case PIXELFORMAT_BC4_UNORM:
 	case PIXELFORMAT_BC4_SNORM:
 	case PIXELFORMAT_BC4_SNORM:
 	case PIXELFORMAT_BC5_UNORM:
 	case PIXELFORMAT_BC5_UNORM:
 	case PIXELFORMAT_BC5_SNORM:
 	case PIXELFORMAT_BC5_SNORM:
-		return (GLAD_VERSION_3_0 || GLAD_ARB_texture_compression_rgtc || GLAD_EXT_texture_compression_rgtc);
+		if (GLAD_VERSION_3_0 || GLAD_ARB_texture_compression_rgtc || GLAD_EXT_texture_compression_rgtc)
+			flags |= commonsample;
+		break;
 	case PIXELFORMAT_BC6H_UFLOAT:
 	case PIXELFORMAT_BC6H_UFLOAT:
 	case PIXELFORMAT_BC6H_FLOAT:
 	case PIXELFORMAT_BC6H_FLOAT:
 	case PIXELFORMAT_BC7_UNORM:
 	case PIXELFORMAT_BC7_UNORM:
-		return GLAD_VERSION_4_2 || GLAD_ARB_texture_compression_bptc;
+		if (GLAD_VERSION_4_2 || GLAD_ARB_texture_compression_bptc)
+			flags |= commonsample;
+		break;
 	case PIXELFORMAT_PVR1_RGB2_UNORM:
 	case PIXELFORMAT_PVR1_RGB2_UNORM:
 	case PIXELFORMAT_PVR1_RGB4_UNORM:
 	case PIXELFORMAT_PVR1_RGB4_UNORM:
 	case PIXELFORMAT_PVR1_RGBA2_UNORM:
 	case PIXELFORMAT_PVR1_RGBA2_UNORM:
 	case PIXELFORMAT_PVR1_RGBA4_UNORM:
 	case PIXELFORMAT_PVR1_RGBA4_UNORM:
-		return isSRGB ? GLAD_EXT_pvrtc_sRGB : GLAD_IMG_texture_compression_pvrtc;
+		if (GLAD_IMG_texture_compression_pvrtc)
+			flags |= commonsample;
+		break;
 	case PIXELFORMAT_ETC1_UNORM:
 	case PIXELFORMAT_ETC1_UNORM:
 		// ETC2 support guarantees ETC1 support as well.
 		// ETC2 support guarantees ETC1 support as well.
-		return GLAD_ES_VERSION_3_0 || GLAD_VERSION_4_3 || GLAD_ARB_ES3_compatibility || GLAD_OES_compressed_ETC1_RGB8_texture;
+		if (GLAD_ES_VERSION_3_0 || GLAD_VERSION_4_3 || GLAD_ARB_ES3_compatibility || GLAD_OES_compressed_ETC1_RGB8_texture)
+			flags |= commonsample;
+		break;
 	case PIXELFORMAT_ETC2_RGB_UNORM:
 	case PIXELFORMAT_ETC2_RGB_UNORM:
 	case PIXELFORMAT_ETC2_RGBA_UNORM:
 	case PIXELFORMAT_ETC2_RGBA_UNORM:
 	case PIXELFORMAT_ETC2_RGBA1_UNORM:
 	case PIXELFORMAT_ETC2_RGBA1_UNORM:
@@ -2086,7 +2104,9 @@ bool OpenGL::isPixelFormatSupported(PixelFormat pixelformat, bool rendertarget,
 	case PIXELFORMAT_EAC_R_SNORM:
 	case PIXELFORMAT_EAC_R_SNORM:
 	case PIXELFORMAT_EAC_RG_UNORM:
 	case PIXELFORMAT_EAC_RG_UNORM:
 	case PIXELFORMAT_EAC_RG_SNORM:
 	case PIXELFORMAT_EAC_RG_SNORM:
-		return GLAD_ES_VERSION_3_0 || GLAD_VERSION_4_3 || GLAD_ARB_ES3_compatibility;
+		if (GLAD_ES_VERSION_3_0 || GLAD_VERSION_4_3 || GLAD_ARB_ES3_compatibility)
+			flags |= commonsample;
+		break;
 	case PIXELFORMAT_ASTC_4x4:
 	case PIXELFORMAT_ASTC_4x4:
 	case PIXELFORMAT_ASTC_5x4:
 	case PIXELFORMAT_ASTC_5x4:
 	case PIXELFORMAT_ASTC_5x5:
 	case PIXELFORMAT_ASTC_5x5:
@@ -2101,28 +2121,18 @@ bool OpenGL::isPixelFormatSupported(PixelFormat pixelformat, bool rendertarget,
 	case PIXELFORMAT_ASTC_10x10:
 	case PIXELFORMAT_ASTC_10x10:
 	case PIXELFORMAT_ASTC_12x10:
 	case PIXELFORMAT_ASTC_12x10:
 	case PIXELFORMAT_ASTC_12x12:
 	case PIXELFORMAT_ASTC_12x12:
-		return GLAD_ES_VERSION_3_2 || GLAD_KHR_texture_compression_astc_ldr;
+		if (GLAD_ES_VERSION_3_2 || GLAD_KHR_texture_compression_astc_ldr)
+			flags |= commonsample;
+		break;
 
 
-	default:
-		return false;
+	case PIXELFORMAT_UNKNOWN:
+	case PIXELFORMAT_NORMAL:
+	case PIXELFORMAT_HDR:
+	case PIXELFORMAT_MAX_ENUM:
+		break;
 	}
 	}
-}
 
 
-bool OpenGL::hasTextureFilteringSupport(PixelFormat pixelformat)
-{
-	switch (pixelformat)
-	{
-	case PIXELFORMAT_R16_FLOAT:
-	case PIXELFORMAT_RG16_FLOAT:
-	case PIXELFORMAT_RGBA16_FLOAT:
-		return GLAD_VERSION_1_1 || GLAD_ES_VERSION_3_0 || GLAD_OES_texture_half_float_linear;
-	case PIXELFORMAT_R32_FLOAT:
-	case PIXELFORMAT_RG32_FLOAT:
-	case PIXELFORMAT_RGBA32_FLOAT:
-		return GLAD_VERSION_1_1 || GLAD_OES_texture_float_linear;
-	default:
-		return true;
-	}
+	return flags;
 }
 }
 
 
 const char *OpenGL::errorString(GLenum errorcode)
 const char *OpenGL::errorString(GLenum errorcode)

+ 8 - 15
src/modules/graphics/opengl/OpenGL.h

@@ -235,7 +235,7 @@ public:
 	 * NOTE: This does not account for multiple VAOs being used! Index buffer
 	 * NOTE: This does not account for multiple VAOs being used! Index buffer
 	 * bindings are per-VAO in OpenGL, but this doesn't know about that.
 	 * bindings are per-VAO in OpenGL, but this doesn't know about that.
 	 **/
 	 **/
-	void bindBuffer(BufferType type, GLuint buffer);
+	void bindBuffer(BufferUsage type, GLuint buffer);
 
 
 	/**
 	/**
 	 * glDeleteBuffers which updates our shadowed state.
 	 * glDeleteBuffers which updates our shadowed state.
@@ -270,12 +270,6 @@ public:
 	 **/
 	 **/
 	void setScissor(const Rect &v, bool rtActive);
 	void setScissor(const Rect &v, bool rtActive);
 
 
-	/**
-	 * Sets the global point size.
-	 **/
-	void setPointSize(float size);
-	float getPointSize() const;
-
 	/**
 	/**
 	 * State-tracked version of glEnable.
 	 * State-tracked version of glEnable.
 	 **/
 	 **/
@@ -341,7 +335,7 @@ public:
 
 
 	void bindBufferTextureToUnit(GLuint texture, int textureunit, bool restoreprev, bool bindforedit);
 	void bindBufferTextureToUnit(GLuint texture, int textureunit, bool restoreprev, bool bindforedit);
 
 
-	void bindIndexedBuffer(GLuint buffer, BufferType type, int index);
+	void bindIndexedBuffer(GLuint buffer, BufferUsage type, int index);
 
 
 	/**
 	/**
 	 * Helper for deleting an OpenGL texture.
 	 * Helper for deleting an OpenGL texture.
@@ -362,7 +356,7 @@ public:
 	bool rawTexStorage(TextureType target, int levels, PixelFormat pixelformat, bool &isSRGB, int width, int height, int depth = 1);
 	bool rawTexStorage(TextureType target, int levels, PixelFormat pixelformat, bool &isSRGB, int width, int height, int depth = 1);
 
 
 	bool isTextureTypeSupported(TextureType type) const;
 	bool isTextureTypeSupported(TextureType type) const;
-	bool isBufferTypeSupported(BufferType type) const;
+	bool isBufferUsageSupported(BufferUsage usage) const;
 	bool isClampZeroOneTextureWrapSupported() const;
 	bool isClampZeroOneTextureWrapSupported() const;
 	bool isPixelShaderHighpSupported() const;
 	bool isPixelShaderHighpSupported() const;
 	bool isInstancingSupported() const;
 	bool isInstancingSupported() const;
@@ -433,18 +427,17 @@ public:
 	Vendor getVendor() const;
 	Vendor getVendor() const;
 
 
 	static GLenum getGLPrimitiveType(PrimitiveType type);
 	static GLenum getGLPrimitiveType(PrimitiveType type);
-	static GLenum getGLBufferType(BufferType type);
+	static GLenum getGLBufferType(BufferUsage usage);
 	static GLenum getGLIndexDataType(IndexDataType type);
 	static GLenum getGLIndexDataType(IndexDataType type);
 	static GLenum getGLVertexDataType(DataFormat format, int &components, GLboolean &normalized, bool &intformat);
 	static GLenum getGLVertexDataType(DataFormat format, int &components, GLboolean &normalized, bool &intformat);
-	static GLenum getGLBufferUsage(BufferUsage usage);
+	static GLenum getGLBufferDataUsage(BufferDataUsage usage);
 	static GLenum getGLTextureType(TextureType type);
 	static GLenum getGLTextureType(TextureType type);
 	static GLint getGLWrapMode(SamplerState::WrapMode wmode);
 	static GLint getGLWrapMode(SamplerState::WrapMode wmode);
 	static GLint getGLCompareMode(CompareMode mode);
 	static GLint getGLCompareMode(CompareMode mode);
 
 
 	static TextureFormat convertPixelFormat(PixelFormat pixelformat, bool renderbuffer, bool &isSRGB);
 	static TextureFormat convertPixelFormat(PixelFormat pixelformat, bool renderbuffer, bool &isSRGB);
 	static bool isTexStorageSupported();
 	static bool isTexStorageSupported();
-	static bool isPixelFormatSupported(PixelFormat pixelformat, bool rendertarget, bool readable, bool isSRGB);
-	static bool hasTextureFilteringSupport(PixelFormat pixelformat);
+	static uint32 getPixelFormatUsageFlags(PixelFormat pixelformat);
 
 
 	static const char *errorString(GLenum errorcode);
 	static const char *errorString(GLenum errorcode);
 	static const char *framebufferStatusString(GLenum status);
 	static const char *framebufferStatusString(GLenum status);
@@ -487,12 +480,12 @@ private:
 	// Tracked OpenGL state.
 	// Tracked OpenGL state.
 	struct
 	struct
 	{
 	{
-		GLuint boundBuffers[BUFFERTYPE_MAX_ENUM];
+		GLuint boundBuffers[BUFFERUSAGE_MAX_ENUM];
 
 
 		// Texture unit state (currently bound texture for each texture unit.)
 		// Texture unit state (currently bound texture for each texture unit.)
 		std::vector<GLuint> boundTextures[TEXTURE_MAX_ENUM + 1];
 		std::vector<GLuint> boundTextures[TEXTURE_MAX_ENUM + 1];
 
 
-		std::vector<GLuint> boundIndexedBuffers[BUFFERTYPE_MAX_ENUM];
+		std::vector<GLuint> boundIndexedBuffers[BUFFERUSAGE_MAX_ENUM];
 
 
 		bool enableState[ENABLE_MAX_ENUM];
 		bool enableState[ENABLE_MAX_ENUM];
 
 

+ 12 - 24
src/modules/graphics/opengl/Shader.cpp

@@ -48,7 +48,6 @@ Shader::Shader(love::graphics::ShaderStage *vertex, love::graphics::ShaderStage
 	, program(0)
 	, program(0)
 	, builtinUniforms()
 	, builtinUniforms()
 	, builtinUniformInfo()
 	, builtinUniformInfo()
-	, lastPointSize(0.0f)
 {
 {
 	// load shader source and create program object
 	// load shader source and create program object
 	loadVolatile();
 	loadVolatile();
@@ -315,7 +314,7 @@ void Shader::mapActiveUniforms()
 		}
 		}
 	}
 	}
 
 
-	if (gl.isBufferTypeSupported(BUFFERTYPE_SHADER_STORAGE))
+	if (gl.isBufferUsageSupported(BUFFERUSAGE_SHADER_STORAGE))
 	{
 	{
 		GLint numstoragebuffers = 0;
 		GLint numstoragebuffers = 0;
 		glGetProgramInterfaceiv(program, GL_SHADER_STORAGE_BLOCK, GL_ACTIVE_RESOURCES, &numstoragebuffers);
 		glGetProgramInterfaceiv(program, GL_SHADER_STORAGE_BLOCK, GL_ACTIVE_RESOURCES, &numstoragebuffers);
@@ -430,8 +429,6 @@ bool Shader::loadVolatile()
 {
 {
 	OpenGL::TempDebugGroup debuggroup("Shader load");
 	OpenGL::TempDebugGroup debuggroup("Shader load");
 
 
-	lastPointSize = -1.0f;
-
 	// zero out active texture list
 	// zero out active texture list
 	textureUnits.clear();
 	textureUnits.clear();
 	textureUnits.push_back(TextureUnit());
 	textureUnits.push_back(TextureUnit());
@@ -577,7 +574,7 @@ void Shader::attach()
 		}
 		}
 
 
 		for (auto bufferbinding : activeStorageBufferBindings)
 		for (auto bufferbinding : activeStorageBufferBindings)
-			gl.bindIndexedBuffer(bufferbinding.buffer, BUFFERTYPE_SHADER_STORAGE, bufferbinding.bindingindex);
+			gl.bindIndexedBuffer(bufferbinding.buffer, BUFFERUSAGE_SHADER_STORAGE, bufferbinding.bindingindex);
 
 
 		// send any pending uniforms to the shader program.
 		// send any pending uniforms to the shader program.
 		for (const auto &p : pendingUniformUpdates)
 		for (const auto &p : pendingUniformUpdates)
@@ -807,9 +804,9 @@ void Shader::sendBuffers(const UniformInfo *info, love::graphics::Buffer **buffe
 	bool storagebinding = info->baseType == UNIFORM_STORAGEBUFFER;
 	bool storagebinding = info->baseType == UNIFORM_STORAGEBUFFER;
 
 
 	if (texelbinding)
 	if (texelbinding)
-		requiredtypeflags = Buffer::TYPEFLAG_TEXEL;
+		requiredtypeflags = BUFFERUSAGEFLAG_TEXEL;
 	else if (storagebinding)
 	else if (storagebinding)
-		requiredtypeflags = Buffer::TYPEFLAG_SHADER_STORAGE;
+		requiredtypeflags = BUFFERUSAGEFLAG_SHADER_STORAGE;
 
 
 	if (requiredtypeflags == 0)
 	if (requiredtypeflags == 0)
 		return;
 		return;
@@ -828,7 +825,7 @@ void Shader::sendBuffers(const UniformInfo *info, love::graphics::Buffer **buffe
 
 
 		if (buffer != nullptr)
 		if (buffer != nullptr)
 		{
 		{
-			if ((buffer->getTypeFlags() & requiredtypeflags) == 0)
+			if ((buffer->getUsageFlags() & requiredtypeflags) == 0)
 			{
 			{
 				if (internalUpdate)
 				if (internalUpdate)
 					continue;
 					continue;
@@ -906,7 +903,7 @@ void Shader::sendBuffers(const UniformInfo *info, love::graphics::Buffer **buffe
 				glbuffer = gl.getDefaultStorageBuffer();
 				glbuffer = gl.getDefaultStorageBuffer();
 
 
 			if (shaderactive)
 			if (shaderactive)
-				gl.bindIndexedBuffer(glbuffer, BUFFERTYPE_SHADER_STORAGE, bindingindex);
+				gl.bindIndexedBuffer(glbuffer, BUFFERUSAGE_SHADER_STORAGE, bindingindex);
 
 
 			int activeindex = storageBufferBindingIndexToActiveBinding[bindingindex];
 			int activeindex = storageBufferBindingIndexToActiveBinding[bindingindex];
 			if (activeindex >= 0)
 			if (activeindex >= 0)
@@ -962,26 +959,11 @@ void Shader::setVideoTextures(love::graphics::Texture *ytexture, love::graphics:
 	}
 	}
 }
 }
 
 
-void Shader::updatePointSize(float size)
-{
-	if (size == lastPointSize || current != this)
-		return;
-
-	GLint location = builtinUniforms[BUILTIN_POINT_SIZE];
-	if (location >= 0)
-		glUniform1f(location, size);
-
-	lastPointSize = size;
-}
-
 void Shader::updateBuiltinUniforms(love::graphics::Graphics *gfx, int viewportW, int viewportH)
 void Shader::updateBuiltinUniforms(love::graphics::Graphics *gfx, int viewportW, int viewportH)
 {
 {
 	if (current != this)
 	if (current != this)
 		return;
 		return;
 
 
-	if (GLAD_ES_VERSION_2_0)
-		updatePointSize(gl.getPointSize());
-
 	BuiltinUniformData data;
 	BuiltinUniformData data;
 
 
 	data.transformMatrix = gfx->getTransform();
 	data.transformMatrix = gfx->getTransform();
@@ -1001,6 +983,12 @@ void Shader::updateBuiltinUniforms(love::graphics::Graphics *gfx, int viewportW,
 		}
 		}
 	}
 	}
 
 
+	// Store DPI scale in an unused component of another vector.
+	data.normalMatrix[0].w = (float) gfx->getCurrentDPIScale();
+
+	// Same with point size.
+	data.normalMatrix[1].w = gfx->getPointSize();
+
 	data.screenSizeParams.x = viewportW;
 	data.screenSizeParams.x = viewportW;
 	data.screenSizeParams.y = viewportH;
 	data.screenSizeParams.y = viewportH;
 
 

+ 0 - 7
src/modules/graphics/opengl/Shader.h

@@ -43,10 +43,6 @@ class Shader final : public love::graphics::Shader, public Volatile
 {
 {
 public:
 public:
 
 
-	/**
-	 * Creates a new Shader using a list of source codes.
-	 * Source must contain either vertex or pixel shader code, or both.
-	 **/
 	Shader(love::graphics::ShaderStage *vertex, love::graphics::ShaderStage *pixel);
 	Shader(love::graphics::ShaderStage *vertex, love::graphics::ShaderStage *pixel);
 	virtual ~Shader();
 	virtual ~Shader();
 
 
@@ -67,7 +63,6 @@ public:
 	ptrdiff_t getHandle() const override;
 	ptrdiff_t getHandle() const override;
 	void setVideoTextures(love::graphics::Texture *ytexture, love::graphics::Texture *cbtexture, love::graphics::Texture *crtexture) override;
 	void setVideoTextures(love::graphics::Texture *ytexture, love::graphics::Texture *cbtexture, love::graphics::Texture *crtexture) override;
 
 
-	void updatePointSize(float size);
 	void updateBuiltinUniforms(love::graphics::Graphics *gfx, int viewportW, int viewportH);
 	void updateBuiltinUniforms(love::graphics::Graphics *gfx, int viewportW, int viewportH);
 
 
 private:
 private:
@@ -125,8 +120,6 @@ private:
 
 
 	std::vector<std::pair<const UniformInfo *, int>> pendingUniformUpdates;
 	std::vector<std::pair<const UniformInfo *, int>> pendingUniformUpdates;
 
 
-	float lastPointSize;
-
 }; // Shader
 }; // Shader
 
 
 } // opengl
 } // opengl

+ 7 - 7
src/modules/graphics/opengl/StreamBuffer.cpp

@@ -44,7 +44,7 @@ class StreamBufferClientMemory final : public love::graphics::StreamBuffer
 {
 {
 public:
 public:
 
 
-	StreamBufferClientMemory(BufferType mode, size_t size)
+	StreamBufferClientMemory(BufferUsage mode, size_t size)
 		: love::graphics::StreamBuffer(mode, size)
 		: love::graphics::StreamBuffer(mode, size)
 		, data(nullptr)
 		, data(nullptr)
 	{
 	{
@@ -86,7 +86,7 @@ class StreamBufferSubDataOrphan final : public love::graphics::StreamBuffer, pub
 {
 {
 public:
 public:
 
 
-	StreamBufferSubDataOrphan(BufferType mode, size_t size)
+	StreamBufferSubDataOrphan(BufferUsage mode, size_t size)
 		: love::graphics::StreamBuffer(mode, size)
 		: love::graphics::StreamBuffer(mode, size)
 		, vbo(0)
 		, vbo(0)
 		, glMode(OpenGL::getGLBufferType(mode))
 		, glMode(OpenGL::getGLBufferType(mode))
@@ -184,7 +184,7 @@ class StreamBufferSync : public love::graphics::StreamBuffer
 {
 {
 public:
 public:
 
 
-	StreamBufferSync(BufferType type, size_t size)
+	StreamBufferSync(BufferUsage type, size_t size)
 		: love::graphics::StreamBuffer(type, size)
 		: love::graphics::StreamBuffer(type, size)
 		, frameIndex(0)
 		, frameIndex(0)
 		, syncs()
 		, syncs()
@@ -220,7 +220,7 @@ class StreamBufferMapSync final : public StreamBufferSync, public Volatile
 {
 {
 public:
 public:
 
 
-	StreamBufferMapSync(BufferType type, size_t size)
+	StreamBufferMapSync(BufferUsage type, size_t size)
 		: StreamBufferSync(type, size)
 		: StreamBufferSync(type, size)
 		, vbo(0)
 		, vbo(0)
 		, glMode(OpenGL::getGLBufferType(mode))
 		, glMode(OpenGL::getGLBufferType(mode))
@@ -302,7 +302,7 @@ public:
 
 
 	// Coherent mapping is supposedly faster on intel/nvidia aside from a couple
 	// Coherent mapping is supposedly faster on intel/nvidia aside from a couple
 	// old nvidia GPUs.
 	// old nvidia GPUs.
-	StreamBufferPersistentMapSync(BufferType type, size_t size, bool coherent = true)
+	StreamBufferPersistentMapSync(BufferUsage type, size_t size, bool coherent = true)
 		: StreamBufferSync(type, size)
 		: StreamBufferSync(type, size)
 		, vbo(0)
 		, vbo(0)
 		, glMode(OpenGL::getGLBufferType(mode))
 		, glMode(OpenGL::getGLBufferType(mode))
@@ -393,7 +393,7 @@ class StreamBufferPinnedMemory final : public StreamBufferSync, public Volatile
 {
 {
 public:
 public:
 
 
-	StreamBufferPinnedMemory(BufferType type, size_t size)
+	StreamBufferPinnedMemory(BufferUsage type, size_t size)
 		: StreamBufferSync(type, size)
 		: StreamBufferSync(type, size)
 		, vbo(0)
 		, vbo(0)
 		, glMode(OpenGL::getGLBufferType(mode))
 		, glMode(OpenGL::getGLBufferType(mode))
@@ -491,7 +491,7 @@ private:
 
 
 }; // StreamBufferPinnedMemory
 }; // StreamBufferPinnedMemory
 
 
-love::graphics::StreamBuffer *CreateStreamBuffer(BufferType mode, size_t size)
+love::graphics::StreamBuffer *CreateStreamBuffer(BufferUsage mode, size_t size)
 {
 {
 	if (gl.isCoreProfile())
 	if (gl.isCoreProfile())
 	{
 	{

+ 1 - 1
src/modules/graphics/opengl/StreamBuffer.h

@@ -29,7 +29,7 @@ namespace graphics
 namespace opengl
 namespace opengl
 {
 {
 
 
-love::graphics::StreamBuffer *CreateStreamBuffer(BufferType mode, size_t size);
+love::graphics::StreamBuffer *CreateStreamBuffer(BufferUsage mode, size_t size);
 
 
 } // opengl
 } // opengl
 } // graphics
 } // graphics

+ 5 - 13
src/modules/graphics/opengl/Texture.cpp

@@ -251,7 +251,7 @@ void Texture::createTexture()
 		int slices = texType == TEXTURE_CUBE ? 6 : 1;
 		int slices = texType == TEXTURE_CUBE ? 6 : 1;
 		Rect rect = {0, 0, 2, 2};
 		Rect rect = {0, 0, 2, 2};
 		for (int slice = 0; slice < slices; slice++)
 		for (int slice = 0; slice < slices; slice++)
-			uploadByteData(PIXELFORMAT_RGBA8_UNORM, px, sizeof(px), 0, slice, rect, nullptr);
+			uploadByteData(PIXELFORMAT_RGBA8_UNORM, px, sizeof(px), 0, slice, rect);
 
 
 		return;
 		return;
 	}
 	}
@@ -452,18 +452,8 @@ void Texture::unloadVolatile()
 	setGraphicsMemorySize(0);
 	setGraphicsMemorySize(0);
 }
 }
 
 
-void Texture::uploadByteData(PixelFormat pixelformat, const void *data, size_t size, int level, int slice, const Rect &r, love::image::ImageDataBase *imgd)
+void Texture::uploadByteData(PixelFormat pixelformat, const void *data, size_t size, int level, int slice, const Rect &r)
 {
 {
-	love::image::ImageDataBase *oldd = slices.get(slice, level);
-
-	// We can only replace the internal Data (used when reloading due to setMode)
-	// if the dimensions match.
-	if (imgd != nullptr && oldd != nullptr && oldd->getWidth() == imgd->getWidth()
-		&& oldd->getHeight() == imgd->getHeight())
-	{
-		slices.set(slice, level, imgd);
-	}
-
 	OpenGL::TempDebugGroup debuggroup("Texture data upload");
 	OpenGL::TempDebugGroup debuggroup("Texture data upload");
 
 
 	gl.bindTextureToUnit(this, 0, false);
 	gl.bindTextureToUnit(this, 0, false);
@@ -539,7 +529,9 @@ void Texture::setSamplerState(const SamplerState &s)
 	// Base class does common validation and assigns samplerState.
 	// Base class does common validation and assigns samplerState.
 	love::graphics::Texture::setSamplerState(s);
 	love::graphics::Texture::setSamplerState(s);
 
 
-	if (!OpenGL::hasTextureFilteringSupport(getPixelFormat()))
+	auto supportedflags = OpenGL::getPixelFormatUsageFlags(getPixelFormat());
+
+	if ((supportedflags & PIXELFORMATUSAGEFLAGS_LINEAR) == 0)
 	{
 	{
 		samplerState.magFilter = samplerState.minFilter = SamplerState::FILTER_NEAREST;
 		samplerState.magFilter = samplerState.minFilter = SamplerState::FILTER_NEAREST;
 
 

+ 1 - 1
src/modules/graphics/opengl/Texture.h

@@ -58,7 +58,7 @@ private:
 
 
 	void createTexture();
 	void createTexture();
 
 
-	void uploadByteData(PixelFormat pixelformat, const void *data, size_t size, int level, int slice, const Rect &r, love::image::ImageDataBase *imgd = nullptr) override;
+	void uploadByteData(PixelFormat pixelformat, const void *data, size_t size, int level, int slice, const Rect &r) override;
 
 
 	void generateMipmapsInternal() override;
 	void generateMipmapsInternal() override;
 
 

+ 13 - 11
src/modules/graphics/vertex.cpp

@@ -337,14 +337,16 @@ const char *getConstant(BuiltinVertexAttribute attrib)
 	return name;
 	return name;
 }
 }
 
 
-STRINGMAP_BEGIN(BufferType, BUFFERTYPE_MAX_ENUM, bufferTypeName)
+STRINGMAP_BEGIN(BufferUsage, BUFFERUSAGE_MAX_ENUM, bufferUsageName)
 {
 {
-	{ "vertex",        BUFFERTYPE_VERTEX         },
-	{ "index",         BUFFERTYPE_INDEX          },
-	{ "texel",         BUFFERTYPE_TEXEL          },
-	{ "shaderstorage", BUFFERTYPE_SHADER_STORAGE },
+	{ "vertex",        BUFFERUSAGE_VERTEX         },
+	{ "index",         BUFFERUSAGE_INDEX          },
+	{ "texel",         BUFFERUSAGE_TEXEL          },
+	{ "shaderstorage", BUFFERUSAGE_SHADER_STORAGE },
+	{ "copysource",    BUFFERUSAGE_COPY_SOURCE    },
+	{ "copydest",      BUFFERUSAGE_COPY_DEST      },
 }
 }
-STRINGMAP_END(BufferType, BUFFERTYPE_MAX_ENUM, bufferTypeName)
+STRINGMAP_END(BufferUsage, BUFFERUSAGE_MAX_ENUM, bufferUsageName)
 
 
 STRINGMAP_BEGIN(IndexDataType, INDEX_MAX_ENUM, indexType)
 STRINGMAP_BEGIN(IndexDataType, INDEX_MAX_ENUM, indexType)
 {
 {
@@ -353,13 +355,13 @@ STRINGMAP_BEGIN(IndexDataType, INDEX_MAX_ENUM, indexType)
 }
 }
 STRINGMAP_END(IndexDataType, INDEX_MAX_ENUM, indexType)
 STRINGMAP_END(IndexDataType, INDEX_MAX_ENUM, indexType)
 
 
-STRINGMAP_BEGIN(BufferUsage, BUFFERUSAGE_MAX_ENUM, bufferUsage)
+STRINGMAP_BEGIN(BufferDataUsage, BUFFERDATAUSAGE_MAX_ENUM, bufferDataUsage)
 {
 {
-	{ "stream",  BUFFERUSAGE_STREAM  },
-	{ "dynamic", BUFFERUSAGE_DYNAMIC },
-	{ "static",  BUFFERUSAGE_STATIC  },
+	{ "stream",  BUFFERDATAUSAGE_STREAM  },
+	{ "dynamic", BUFFERDATAUSAGE_DYNAMIC },
+	{ "static",  BUFFERDATAUSAGE_STATIC  },
 }
 }
-STRINGMAP_END(BufferUsage, BUFFERUSAGE_MAX_ENUM, bufferUsage)
+STRINGMAP_END(BufferDataUsage, BUFFERDATAUSAGE_MAX_ENUM, bufferDataUsage)
 
 
 STRINGMAP_BEGIN(PrimitiveType, PRIMITIVE_MAX_ENUM, primitiveType)
 STRINGMAP_BEGIN(PrimitiveType, PRIMITIVE_MAX_ENUM, primitiveType)
 {
 {

+ 28 - 14
src/modules/graphics/vertex.h

@@ -54,14 +54,28 @@ enum BuiltinVertexAttributeFlags
 	ATTRIBFLAG_COLOR = 1 << ATTRIB_COLOR,
 	ATTRIBFLAG_COLOR = 1 << ATTRIB_COLOR,
 };
 };
 
 
-enum BufferType
+enum BufferUsage
+{
+	BUFFERUSAGE_VERTEX = 0,
+	BUFFERUSAGE_INDEX,
+	BUFFERUSAGE_UNIFORM,
+	BUFFERUSAGE_TEXEL,
+	BUFFERUSAGE_SHADER_STORAGE,
+	BUFFERUSAGE_COPY_SOURCE,
+	BUFFERUSAGE_COPY_DEST,
+	BUFFERUSAGE_MAX_ENUM
+};
+
+enum BufferUsageFlags
 {
 {
-	BUFFERTYPE_VERTEX = 0,
-	BUFFERTYPE_INDEX,
-	BUFFERTYPE_UNIFORM,
-	BUFFERTYPE_TEXEL,
-	BUFFERTYPE_SHADER_STORAGE,
-	BUFFERTYPE_MAX_ENUM
+	BUFFERUSAGEFLAG_NONE = 0,
+	BUFFERUSAGEFLAG_VERTEX = 1 << BUFFERUSAGE_VERTEX,
+	BUFFERUSAGEFLAG_INDEX = 1 << BUFFERUSAGE_INDEX,
+	BUFFERUSAGEFLAG_UNIFORM = 1 << BUFFERUSAGE_UNIFORM,
+	BUFFERUSAGEFLAG_TEXEL = 1 << BUFFERUSAGE_TEXEL,
+	BUFFERUSAGEFLAG_SHADER_STORAGE = 1 << BUFFERUSAGE_SHADER_STORAGE,
+	BUFFERUSAGEFLAG_COPY_SOURCE = 1 << BUFFERUSAGE_COPY_SOURCE,
+	BUFFERUSAGEFLAG_COPY_DEST = 1 << BUFFERUSAGE_COPY_DEST,
 };
 };
 
 
 enum IndexDataType
 enum IndexDataType
@@ -97,12 +111,12 @@ enum CullMode
 };
 };
 
 
 // The expected usage pattern of buffer data.
 // The expected usage pattern of buffer data.
-enum BufferUsage
+enum BufferDataUsage
 {
 {
-	BUFFERUSAGE_STREAM,
-	BUFFERUSAGE_DYNAMIC,
-	BUFFERUSAGE_STATIC,
-	BUFFERUSAGE_MAX_ENUM
+	BUFFERDATAUSAGE_STREAM,
+	BUFFERDATAUSAGE_DYNAMIC,
+	BUFFERDATAUSAGE_STATIC,
+	BUFFERDATAUSAGE_MAX_ENUM
 };
 };
 
 
 // Value types used when interfacing with the GPU (vertex and shader data).
 // Value types used when interfacing with the GPU (vertex and shader data).
@@ -380,9 +394,9 @@ void fillIndices(TriangleIndexMode mode, uint16 vertexStart, uint16 vertexCount,
 void fillIndices(TriangleIndexMode mode, uint32 vertexStart, uint32 vertexCount, uint32 *indices);
 void fillIndices(TriangleIndexMode mode, uint32 vertexStart, uint32 vertexCount, uint32 *indices);
 
 
 STRINGMAP_DECLARE(BuiltinVertexAttribute);
 STRINGMAP_DECLARE(BuiltinVertexAttribute);
-STRINGMAP_DECLARE(BufferType);
-STRINGMAP_DECLARE(IndexDataType);
 STRINGMAP_DECLARE(BufferUsage);
 STRINGMAP_DECLARE(BufferUsage);
+STRINGMAP_DECLARE(IndexDataType);
+STRINGMAP_DECLARE(BufferDataUsage);
 STRINGMAP_DECLARE(PrimitiveType);
 STRINGMAP_DECLARE(PrimitiveType);
 STRINGMAP_DECLARE(AttributeStep);
 STRINGMAP_DECLARE(AttributeStep);
 STRINGMAP_DECLARE(DataFormat);
 STRINGMAP_DECLARE(DataFormat);

+ 41 - 22
src/modules/graphics/wrap_Buffer.cpp

@@ -190,34 +190,49 @@ static int w_Buffer_setArrayData(lua_State *L)
 {
 {
 	Buffer *t = luax_checkbuffer(L, 1);
 	Buffer *t = luax_checkbuffer(L, 1);
 
 
-	int startindex = (int) luaL_optnumber(L, 3, 1) - 1;
+	int sourceindex = (int) luaL_optnumber(L, 3, 1) - 1;
+	int destindex = (int) luaL_optnumber(L, 4, 1) - 1;
+
+	if (sourceindex < 0)
+		return luaL_error(L, "Source start index must be at least 1.");
 
 
 	int count = -1;
 	int count = -1;
-	if (!lua_isnoneornil(L, 4))
+	if (!lua_isnoneornil(L, 5))
 	{
 	{
-		count = (int) luaL_checknumber(L, 4);
+		count = (int) luaL_checknumber(L, 5);
 		if (count <= 0)
 		if (count <= 0)
 			return luaL_error(L, "Element count must be greater than 0.");
 			return luaL_error(L, "Element count must be greater than 0.");
 	}
 	}
 
 
 	size_t stride = t->getArrayStride();
 	size_t stride = t->getArrayStride();
-	size_t offset = startindex * stride;
+	size_t bufferoffset = destindex * stride;
 	int arraylength = (int) t->getArrayLength();
 	int arraylength = (int) t->getArrayLength();
 
 
-	if (startindex >= arraylength || startindex < 0)
-		return luaL_error(L, "Invalid vertex start index (must be between 1 and %d)", arraylength);
+	if (destindex >= arraylength || destindex < 0)
+		return luaL_error(L, "Invalid buffer start index (must be between 1 and %d)", arraylength);
 
 
 	if (luax_istype(L, 2, Data::type))
 	if (luax_istype(L, 2, Data::type))
 	{
 	{
 		Data *d = luax_checktype<Data>(L, 2);
 		Data *d = luax_checktype<Data>(L, 2);
 
 
-		count = count >= 0 ? count : (arraylength - startindex);
-		if (startindex + count > arraylength)
-			return luaL_error(L, "Too many array elements (expected at most %d, got %d)", arraylength - startindex, count);
+		int dataarraylength = d->getSize() / stride;
+
+		if (sourceindex >= dataarraylength)
+			return luaL_error(L, "Invalid data start index (must be between 1 and %d)", dataarraylength);
+
+		int maxcount = std::min(dataarraylength - sourceindex, arraylength - destindex);
+
+		if (count < 0)
+			count = maxcount;
 
 
-		size_t datasize = std::min(d->getSize(), count * stride);
+		if (count > maxcount)
+			return luaL_error(L, "Too many array elements (expected at most %d, got %d)", maxcount, count);
 
 
-		t->fill(offset, datasize, d->getData());
+		size_t dataoffset = sourceindex * stride;
+		size_t datasize = std::min(d->getSize() - dataoffset, count * stride);
+		const void *sourcedata = (const uint8 *) d->getData() + dataoffset;
+
+		t->fill(bufferoffset, datasize, sourcedata);
 		return 0;
 		return 0;
 	}
 	}
 
 
@@ -241,15 +256,19 @@ static int w_Buffer_setArrayData(lua_State *L)
 		tablelen /= ncomponents;
 		tablelen /= ncomponents;
 	}
 	}
 
 
-	count = count >= 0 ? std::min(count, tablelen) : tablelen;
-	if (startindex + count > arraylength)
-		return luaL_error(L, "Too many array elements (expected at most %d, got %d)", arraylength - startindex, count);
+	if (sourceindex >= tablelen)
+		return luaL_error(L, "Invalid data start index (must be between 1 and %d)", tablelen);
+
+	count = count >= 0 ? std::min(count, tablelen - sourceindex) : tablelen - sourceindex;
+
+	if (destindex + count > arraylength)
+		return luaL_error(L, "Too many array elements (expected at most %d, got %d)", arraylength - destindex, count);
 
 
-	char *data = (char *) t->map(Buffer::MAP_WRITE_INVALIDATE, offset, count * stride);
+	char *data = (char *) t->map(Buffer::MAP_WRITE_INVALIDATE, bufferoffset, count * stride);
 
 
 	if (tableoftables)
 	if (tableoftables)
 	{
 	{
-		for (int i = 0; i < count; i++)
+		for (int i = sourceindex; i < count; i++)
 		{
 		{
 			// get arraydata[index]
 			// get arraydata[index]
 			lua_rawgeti(L, 2, i + 1);
 			lua_rawgeti(L, 2, i + 1);
@@ -273,7 +292,7 @@ static int w_Buffer_setArrayData(lua_State *L)
 	}
 	}
 	else // Flat array
 	else // Flat array
 	{
 	{
-		for (int i = 0; i < count; i++)
+		for (int i = sourceindex; i < count; i++)
 		{
 		{
 			// get arraydata[arrayindex * ncomponents + componentindex]
 			// get arraydata[arrayindex * ncomponents + componentindex]
 			for (int componentindex = 1; componentindex <= ncomponents; componentindex++)
 			for (int componentindex = 1; componentindex <= ncomponents; componentindex++)
@@ -292,7 +311,7 @@ static int w_Buffer_setArrayData(lua_State *L)
 		}
 		}
 	}
 	}
 
 
-	t->unmap(offset, count * stride);
+	t->unmap(bufferoffset, count * stride);
 
 
 	return 0;
 	return 0;
 }
 }
@@ -354,11 +373,11 @@ static int w_Buffer_getFormat(lua_State *L)
 static int w_Buffer_isBufferType(lua_State *L)
 static int w_Buffer_isBufferType(lua_State *L)
 {
 {
 	Buffer *t = luax_checkbuffer(L, 1);
 	Buffer *t = luax_checkbuffer(L, 1);
-	BufferType buffertype = BUFFERTYPE_MAX_ENUM;
+	BufferUsage bufferusage = BUFFERUSAGE_MAX_ENUM;
 	const char *typestr = luaL_checkstring(L, 2);
 	const char *typestr = luaL_checkstring(L, 2);
-	if (!getConstant(typestr, buffertype))
-		return luax_enumerror(L, "buffer type", getConstants(buffertype), typestr);
-	luax_pushboolean(L, (t->getTypeFlags() & (1 << buffertype)) != 0);
+	if (!getConstant(typestr, bufferusage))
+		return luax_enumerror(L, "buffer type", getConstants(bufferusage), typestr);
+	luax_pushboolean(L, (t->getUsageFlags() & (1 << bufferusage)) != 0);
 	return 1;
 	return 1;
 }
 }
 
 

+ 36 - 13
src/modules/graphics/wrap_Graphics.cpp

@@ -1325,7 +1325,7 @@ int w_newSpriteBatch(lua_State *L)
 
 
 	Texture *texture = luax_checktexture(L, 1);
 	Texture *texture = luax_checktexture(L, 1);
 	int size = (int) luaL_optinteger(L, 2, 1000);
 	int size = (int) luaL_optinteger(L, 2, 1000);
-	BufferUsage usage = BUFFERUSAGE_DYNAMIC;
+	BufferDataUsage usage = BUFFERDATAUSAGE_DYNAMIC;
 	if (lua_gettop(L) > 2)
 	if (lua_gettop(L) > 2)
 	{
 	{
 		const char *usagestr = luaL_checkstring(L, 3);
 		const char *usagestr = luaL_checkstring(L, 3);
@@ -1488,7 +1488,7 @@ int w_validateShader(lua_State *L)
 	return 1;
 	return 1;
 }
 }
 
 
-static BufferUsage luax_optbufferusage(lua_State *L, int idx, BufferUsage def)
+static BufferDataUsage luax_optdatausage(lua_State *L, int idx, BufferDataUsage def)
 {
 {
 	const char *usagestr = lua_isnoneornil(L, idx) ? nullptr : luaL_checkstring(L, idx);
 	const char *usagestr = lua_isnoneornil(L, idx) ? nullptr : luaL_checkstring(L, idx);
 
 
@@ -1506,7 +1506,7 @@ static void luax_optbuffersettings(lua_State *L, int idx, Buffer::Settings &sett
 	luaL_checktype(L, idx, LUA_TTABLE);
 	luaL_checktype(L, idx, LUA_TTABLE);
 
 
 	lua_getfield(L, idx, "usage");
 	lua_getfield(L, idx, "usage");
-	settings.usage = luax_optbufferusage(L, -1, settings.usage);
+	settings.dataUsage = luax_optdatausage(L, -1, settings.dataUsage);
 	lua_pop(L, 1);
 	lua_pop(L, 1);
 }
 }
 
 
@@ -1664,18 +1664,18 @@ static Buffer *luax_newbuffer(lua_State *L, int idx, Buffer::Settings settings,
 
 
 int w_newBuffer(lua_State *L)
 int w_newBuffer(lua_State *L)
 {
 {
-	Buffer::Settings settings(0, BUFFERUSAGE_DYNAMIC);
+	Buffer::Settings settings(0, BUFFERDATAUSAGE_DYNAMIC);
 
 
 	luaL_checktype(L, 3, LUA_TTABLE);
 	luaL_checktype(L, 3, LUA_TTABLE);
 
 
-	for (int i = 0; i < BUFFERTYPE_MAX_ENUM; i++)
+	for (int i = 0; i < BUFFERUSAGE_MAX_ENUM; i++)
 	{
 	{
-		BufferType buffertype = (BufferType) i;
+		BufferUsage bufferusage = (BufferUsage) i;
 		const char *tname = nullptr;
 		const char *tname = nullptr;
-		if (!getConstant(buffertype, tname))
+		if (!getConstant(bufferusage, tname))
 			continue;
 			continue;
 		if (luax_boolflag(L, 3, tname, false))
 		if (luax_boolflag(L, 3, tname, false))
-			settings.typeFlags = (Buffer::TypeFlags)(settings.typeFlags | (1u << i));
+			settings.usageFlags = (BufferUsageFlags)(settings.usageFlags | (1u << i));
 	}
 	}
 
 
 	luax_optbuffersettings(L, 3, settings);
 	luax_optbuffersettings(L, 3, settings);
@@ -1692,7 +1692,7 @@ int w_newBuffer(lua_State *L)
 
 
 int w_newVertexBuffer(lua_State *L)
 int w_newVertexBuffer(lua_State *L)
 {
 {
-	Buffer::Settings settings(Buffer::TYPEFLAG_VERTEX, BUFFERUSAGE_DYNAMIC);
+	Buffer::Settings settings(BUFFERUSAGEFLAG_VERTEX, BUFFERDATAUSAGE_DYNAMIC);
 	luax_optbuffersettings(L, 3, settings);
 	luax_optbuffersettings(L, 3, settings);
 
 
 	std::vector<Buffer::DataDeclaration> format;
 	std::vector<Buffer::DataDeclaration> format;
@@ -1707,7 +1707,7 @@ int w_newVertexBuffer(lua_State *L)
 
 
 int w_newIndexBuffer(lua_State *L)
 int w_newIndexBuffer(lua_State *L)
 {
 {
-	Buffer::Settings settings(Buffer::TYPEFLAG_INDEX, BUFFERUSAGE_DYNAMIC);
+	Buffer::Settings settings(BUFFERUSAGEFLAG_INDEX, BUFFERDATAUSAGE_DYNAMIC);
 	luax_optbuffersettings(L, 3, settings);
 	luax_optbuffersettings(L, 3, settings);
 
 
 	size_t arraylength = 0;
 	size_t arraylength = 0;
@@ -1799,7 +1799,7 @@ static Mesh *newStandardMesh(lua_State *L)
 	Mesh *t = nullptr;
 	Mesh *t = nullptr;
 
 
 	PrimitiveType drawmode = luax_optmeshdrawmode(L, 2, PRIMITIVE_TRIANGLE_FAN);
 	PrimitiveType drawmode = luax_optmeshdrawmode(L, 2, PRIMITIVE_TRIANGLE_FAN);
-	BufferUsage usage = luax_optbufferusage(L, 3, BUFFERUSAGE_DYNAMIC);
+	BufferDataUsage usage = luax_optdatausage(L, 3, BUFFERDATAUSAGE_DYNAMIC);
 
 
 	std::vector<Buffer::DataDeclaration> format = Mesh::getDefaultVertexFormat();
 	std::vector<Buffer::DataDeclaration> format = Mesh::getDefaultVertexFormat();
 
 
@@ -1861,7 +1861,7 @@ static Mesh *newCustomMesh(lua_State *L)
 	std::vector<Buffer::DataDeclaration> vertexformat;
 	std::vector<Buffer::DataDeclaration> vertexformat;
 
 
 	PrimitiveType drawmode = luax_optmeshdrawmode(L, 3, PRIMITIVE_TRIANGLE_FAN);
 	PrimitiveType drawmode = luax_optmeshdrawmode(L, 3, PRIMITIVE_TRIANGLE_FAN);
-	BufferUsage usage = luax_optbufferusage(L, 4, BUFFERUSAGE_DYNAMIC);
+	BufferDataUsage usage = luax_optdatausage(L, 4, BUFFERDATAUSAGE_DYNAMIC);
 
 
 	lua_rawgeti(L, 1, 1);
 	lua_rawgeti(L, 1, 1);
 	if (!lua_istable(L, -1))
 	if (!lua_istable(L, -1))
@@ -3103,7 +3103,7 @@ int w_rectangle(lua_State *L)
 
 
 	if (lua_isnoneornil(L, 6))
 	if (lua_isnoneornil(L, 6))
 	{
 	{
-		instance()->rectangle(mode, x, y, w, h);
+		luax_catchexcept(L, [&](){ instance()->rectangle(mode, x, y, w, h); });
 		return 0;
 		return 0;
 	}
 	}
 
 
@@ -3255,6 +3255,27 @@ int w_polygon(lua_State *L)
 	return 0;
 	return 0;
 }
 }
 
 
+int w_copyBuffer(lua_State *L)
+{
+	Buffer *source = luax_checkbuffer(L, 1);
+	Buffer *dest = luax_checkbuffer(L, 2);
+
+	ptrdiff_t sourceoffset = luaL_optinteger(L, 3, 0);
+	ptrdiff_t destoffset = luaL_optinteger(L, 4, 0);
+
+	ptrdiff_t size = std::min(source->getSize() - sourceoffset, dest->getSize() - destoffset);
+	if (!lua_isnoneornil(L, 5))
+		size = luaL_checkinteger(L, 5);
+
+	if (sourceoffset < 0 || destoffset < 0)
+		return luaL_error(L, "copyBuffer offsets cannot be negative.");
+	if (size <= 0)
+		return luaL_error(L, "copyBuffer size must be greater than 0.");
+
+	luax_catchexcept(L, [&](){ instance()->copyBuffer(source, dest, sourceoffset, destoffset, size); });
+	return 0;
+}
+
 int w_flushBatch(lua_State *)
 int w_flushBatch(lua_State *)
 {
 {
 	instance()->flushBatchedDraws();
 	instance()->flushBatchedDraws();
@@ -3451,6 +3472,8 @@ static const luaL_Reg functions[] =
 	{ "print", w_print },
 	{ "print", w_print },
 	{ "printf", w_printf },
 	{ "printf", w_printf },
 
 
+	{ "copyBuffer", w_copyBuffer },
+
 	{ "isCreated", w_isCreated },
 	{ "isCreated", w_isCreated },
 	{ "isActive", w_isActive },
 	{ "isActive", w_isActive },
 	{ "isGammaCorrect", w_isGammaCorrect },
 	{ "isGammaCorrect", w_isGammaCorrect },

+ 57 - 82
src/modules/joystick/Joystick.cpp

@@ -42,102 +42,77 @@ float Joystick::clampval(float x)
 	return x;
 	return x;
 }
 }
 
 
-bool Joystick::getConstant(const char *in, Joystick::Hat &out)
+STRINGMAP_CLASS_BEGIN(Joystick, Joystick::Hat, Joystick::HAT_MAX_ENUM, hat)
 {
 {
-	return hats.find(in, out);
+	{ "c",  Joystick::HAT_CENTERED  },
+	{ "u",  Joystick::HAT_UP        },
+	{ "r",  Joystick::HAT_RIGHT     },
+	{ "d",  Joystick::HAT_DOWN      },
+	{ "l",  Joystick::HAT_LEFT      },
+	{ "ru", Joystick::HAT_RIGHTUP   },
+	{ "rd", Joystick::HAT_RIGHTDOWN },
+	{ "lu", Joystick::HAT_LEFTUP    },
+	{ "ld", Joystick::HAT_LEFTDOWN  },
 }
 }
+STRINGMAP_CLASS_END(Joystick, Joystick::Hat, Joystick::HAT_MAX_ENUM, hat)
 
 
-bool Joystick::getConstant(Joystick::Hat in, const char *&out)
+STRINGMAP_CLASS_BEGIN(Joystick, Joystick::GamepadType, Joystick::GAMEPAD_TYPE_MAX_ENUM, gamepadType)
 {
 {
-	return hats.find(in, out);
+	{ "unknown",   Joystick::GAMEPAD_TYPE_UNKNOWN             },
+	{ "xbox360",   Joystick::GAMEPAD_TYPE_XBOX360             },
+	{ "xboxone",   Joystick::GAMEPAD_TYPE_XBOXONE             },
+	{ "ps3",       Joystick::GAMEPAD_TYPE_PS3                 },
+	{ "ps4",       Joystick::GAMEPAD_TYPE_PS4                 },
+	{ "ps5",       Joystick::GAMEPAD_TYPE_PS5                 },
+	{ "switchpro", Joystick::GAMEPAD_TYPE_NINTENDO_SWITCH_PRO },
+	{ "virtual",   Joystick::GAMEPAD_TYPE_VIRTUAL             },
 }
 }
+STRINGMAP_CLASS_END(Joystick, Joystick::GamepadType, Joystick::GAMEPAD_TYPE_MAX_ENUM, gamepadType)
 
 
-bool Joystick::getConstant(const char *in, Joystick::GamepadAxis &out)
+STRINGMAP_CLASS_BEGIN(Joystick, Joystick::GamepadAxis, Joystick::GAMEPAD_AXIS_MAX_ENUM, gpAxis)
 {
 {
-	return gpAxes.find(in, out);
+	{ "leftx",        Joystick::GAMEPAD_AXIS_LEFTX        },
+	{ "lefty",        Joystick::GAMEPAD_AXIS_LEFTY        },
+	{ "rightx",       Joystick::GAMEPAD_AXIS_RIGHTX       },
+	{ "righty",       Joystick::GAMEPAD_AXIS_RIGHTY       },
+	{ "triggerleft",  Joystick::GAMEPAD_AXIS_TRIGGERLEFT  },
+	{ "triggerright", Joystick::GAMEPAD_AXIS_TRIGGERRIGHT },
 }
 }
+STRINGMAP_CLASS_END(Joystick, Joystick::GamepadAxis, Joystick::GAMEPAD_AXIS_MAX_ENUM, gpAxis)
 
 
-bool Joystick::getConstant(Joystick::GamepadAxis in, const char *&out)
+STRINGMAP_CLASS_BEGIN(Joystick, Joystick::GamepadButton, Joystick::GAMEPAD_BUTTON_MAX_ENUM, gpButton)
 {
 {
-	return gpAxes.find(in, out);
+	{ "a",             Joystick::GAMEPAD_BUTTON_A             },
+	{ "b",             Joystick::GAMEPAD_BUTTON_B             },
+	{ "x",             Joystick::GAMEPAD_BUTTON_X             },
+	{ "y",             Joystick::GAMEPAD_BUTTON_Y             },
+	{ "back",          Joystick::GAMEPAD_BUTTON_BACK          },
+	{ "guide",         Joystick::GAMEPAD_BUTTON_GUIDE         },
+	{ "start",         Joystick::GAMEPAD_BUTTON_START         },
+	{ "leftstick",     Joystick::GAMEPAD_BUTTON_LEFTSTICK     },
+	{ "rightstick",    Joystick::GAMEPAD_BUTTON_RIGHTSTICK    },
+	{ "leftshoulder",  Joystick::GAMEPAD_BUTTON_LEFTSHOULDER  },
+	{ "rightshoulder", Joystick::GAMEPAD_BUTTON_RIGHTSHOULDER },
+	{ "dpup",          Joystick::GAMEPAD_BUTTON_DPAD_UP       },
+	{ "dpdown",        Joystick::GAMEPAD_BUTTON_DPAD_DOWN     },
+	{ "dpleft",        Joystick::GAMEPAD_BUTTON_DPAD_LEFT     },
+	{ "dpright",       Joystick::GAMEPAD_BUTTON_DPAD_RIGHT    },
+	{ "misc1",         Joystick::GAMEPAD_BUTTON_MISC1         },
+	{ "paddle1",       Joystick::GAMEPAD_BUTTON_PADDLE1       },
+	{ "paddle2",       Joystick::GAMEPAD_BUTTON_PADDLE2       },
+	{ "paddle3",       Joystick::GAMEPAD_BUTTON_PADDLE3       },
+	{ "paddle4",       Joystick::GAMEPAD_BUTTON_PADDLE4       },
+	{ "touchpad",      Joystick::GAMEPAD_BUTTON_TOUCHPAD      },
 }
 }
+STRINGMAP_CLASS_END(Joystick, Joystick::GamepadButton, Joystick::GAMEPAD_BUTTON_MAX_ENUM, gpButton)
 
 
-bool Joystick::getConstant(const char *in, Joystick::GamepadButton &out)
+STRINGMAP_CLASS_BEGIN(Joystick, Joystick::InputType, Joystick::INPUT_TYPE_MAX_ENUM, inputType)
 {
 {
-	return gpButtons.find(in, out);
+	{ "axis",   Joystick::INPUT_TYPE_AXIS   },
+	{ "button", Joystick::INPUT_TYPE_BUTTON },
+	{ "hat",    Joystick::INPUT_TYPE_HAT    },
 }
 }
-
-bool Joystick::getConstant(Joystick::GamepadButton in, const char *&out)
-{
-	return gpButtons.find(in, out);
-}
-
-bool Joystick::getConstant(const char *in, Joystick::InputType &out)
-{
-	return inputTypes.find(in, out);
-}
-
-bool Joystick::getConstant(Joystick::InputType in, const char *&out)
-{
-	return inputTypes.find(in, out);
-}
-
-StringMap<Joystick::Hat, Joystick::HAT_MAX_ENUM>::Entry Joystick::hatEntries[] =
-{
-	{"c", Joystick::HAT_CENTERED},
-	{"u", Joystick::HAT_UP},
-	{"r", Joystick::HAT_RIGHT},
-	{"d", Joystick::HAT_DOWN},
-	{"l", Joystick::HAT_LEFT},
-	{"ru", Joystick::HAT_RIGHTUP},
-	{"rd", Joystick::HAT_RIGHTDOWN},
-	{"lu", Joystick::HAT_LEFTUP},
-	{"ld", Joystick::HAT_LEFTDOWN},
-};
-
-StringMap<Joystick::Hat, Joystick::HAT_MAX_ENUM> Joystick::hats(Joystick::hatEntries, sizeof(Joystick::hatEntries));
-
-StringMap<Joystick::GamepadAxis, Joystick::GAMEPAD_AXIS_MAX_ENUM>::Entry Joystick::gpAxisEntries[] =
-{
-	{"leftx", GAMEPAD_AXIS_LEFTX},
-	{"lefty", GAMEPAD_AXIS_LEFTY},
-	{"rightx", GAMEPAD_AXIS_RIGHTX},
-	{"righty", GAMEPAD_AXIS_RIGHTY},
-	{"triggerleft", GAMEPAD_AXIS_TRIGGERLEFT},
-	{"triggerright", GAMEPAD_AXIS_TRIGGERRIGHT},
-};
-
-StringMap<Joystick::GamepadAxis, Joystick::GAMEPAD_AXIS_MAX_ENUM> Joystick::gpAxes(Joystick::gpAxisEntries, sizeof(Joystick::gpAxisEntries));
-
-StringMap<Joystick::GamepadButton, Joystick::GAMEPAD_BUTTON_MAX_ENUM>::Entry Joystick::gpButtonEntries[] =
-{
-	{"a", GAMEPAD_BUTTON_A},
-	{"b", GAMEPAD_BUTTON_B},
-	{"x", GAMEPAD_BUTTON_X},
-	{"y", GAMEPAD_BUTTON_Y},
-	{"back", GAMEPAD_BUTTON_BACK},
-	{"guide", GAMEPAD_BUTTON_GUIDE},
-	{"start", GAMEPAD_BUTTON_START},
-	{"leftstick", GAMEPAD_BUTTON_LEFTSTICK},
-	{"rightstick", GAMEPAD_BUTTON_RIGHTSTICK},
-	{"leftshoulder", GAMEPAD_BUTTON_LEFTSHOULDER},
-	{"rightshoulder", GAMEPAD_BUTTON_RIGHTSHOULDER},
-	{"dpup", GAMEPAD_BUTTON_DPAD_UP},
-	{"dpdown", GAMEPAD_BUTTON_DPAD_DOWN},
-	{"dpleft", GAMEPAD_BUTTON_DPAD_LEFT},
-	{"dpright", GAMEPAD_BUTTON_DPAD_RIGHT},
-};
-
-StringMap<Joystick::GamepadButton, Joystick::GAMEPAD_BUTTON_MAX_ENUM> Joystick::gpButtons(Joystick::gpButtonEntries, sizeof(Joystick::gpButtonEntries));
-
-StringMap<Joystick::InputType, Joystick::INPUT_TYPE_MAX_ENUM>::Entry Joystick::inputTypeEntries[] =
-{
-	{"axis", Joystick::INPUT_TYPE_AXIS},
-	{"button", Joystick::INPUT_TYPE_BUTTON},
-	{"hat", Joystick::INPUT_TYPE_HAT},
-};
-
-StringMap<Joystick::InputType, Joystick::INPUT_TYPE_MAX_ENUM> Joystick::inputTypes(Joystick::inputTypeEntries, sizeof(Joystick::inputTypeEntries));
+STRINGMAP_CLASS_END(Joystick, Joystick::InputType, Joystick::INPUT_TYPE_MAX_ENUM, inputType)
 
 
 } // joystick
 } // joystick
 } // love
 } // love

+ 29 - 25
src/modules/joystick/Joystick.h

@@ -56,6 +56,19 @@ public:
 		HAT_MAX_ENUM = 16
 		HAT_MAX_ENUM = 16
 	};
 	};
 
 
+	enum GamepadType
+	{
+		GAMEPAD_TYPE_UNKNOWN,
+		GAMEPAD_TYPE_XBOX360,
+		GAMEPAD_TYPE_XBOXONE,
+		GAMEPAD_TYPE_PS3,
+		GAMEPAD_TYPE_PS4,
+		GAMEPAD_TYPE_PS5,
+		GAMEPAD_TYPE_NINTENDO_SWITCH_PRO,
+		GAMEPAD_TYPE_VIRTUAL,
+		GAMEPAD_TYPE_MAX_ENUM
+	};
+
 	// Valid Gamepad axes.
 	// Valid Gamepad axes.
 	enum GamepadAxis
 	enum GamepadAxis
 	{
 	{
@@ -88,6 +101,12 @@ public:
 		GAMEPAD_BUTTON_DPAD_DOWN,
 		GAMEPAD_BUTTON_DPAD_DOWN,
 		GAMEPAD_BUTTON_DPAD_LEFT,
 		GAMEPAD_BUTTON_DPAD_LEFT,
 		GAMEPAD_BUTTON_DPAD_RIGHT,
 		GAMEPAD_BUTTON_DPAD_RIGHT,
+		GAMEPAD_BUTTON_MISC1, // Xbox Series X share button, PS5 mic button, Switch Pro capture button
+		GAMEPAD_BUTTON_PADDLE1,
+		GAMEPAD_BUTTON_PADDLE2,
+		GAMEPAD_BUTTON_PADDLE3,
+		GAMEPAD_BUTTON_PADDLE4,
+		GAMEPAD_BUTTON_TOUCHPAD,
 		GAMEPAD_BUTTON_MAX_ENUM
 		GAMEPAD_BUTTON_MAX_ENUM
 	};
 	};
 
 
@@ -146,9 +165,14 @@ public:
 
 
 	virtual bool isDown(const std::vector<int> &buttonlist) const = 0;
 	virtual bool isDown(const std::vector<int> &buttonlist) const = 0;
 
 
+	virtual void setPlayerIndex(int index) = 0;
+	virtual int getPlayerIndex() const = 0;
+
 	virtual bool openGamepad(int deviceindex) = 0;
 	virtual bool openGamepad(int deviceindex) = 0;
 	virtual bool isGamepad() const = 0;
 	virtual bool isGamepad() const = 0;
 
 
+	virtual GamepadType getGamepadType() const = 0;
+
 	virtual float getGamepadAxis(GamepadAxis axis) const = 0;
 	virtual float getGamepadAxis(GamepadAxis axis) const = 0;
 	virtual bool isGamepadDown(const std::vector<GamepadButton> &blist) const = 0;
 	virtual bool isGamepadDown(const std::vector<GamepadButton> &blist) const = 0;
 
 
@@ -168,34 +192,14 @@ public:
 	virtual bool setVibration() = 0;
 	virtual bool setVibration() = 0;
 	virtual void getVibration(float &left, float &right) = 0;
 	virtual void getVibration(float &left, float &right) = 0;
 
 
-	static bool getConstant(const char *in, Hat &out);
-	static bool getConstant(Hat in, const char *&out);
-
-	static bool getConstant(const char *in, GamepadAxis &out);
-	static bool getConstant(GamepadAxis in, const char *&out);
-
-	static bool getConstant(const char *in, GamepadButton &out);
-	static bool getConstant(GamepadButton in, const char *&out);
-
-	static bool getConstant(const char *in, InputType &out);
-	static bool getConstant(InputType in, const char *&out);
+	STRINGMAP_CLASS_DECLARE(Hat);
+	STRINGMAP_CLASS_DECLARE(GamepadType);
+	STRINGMAP_CLASS_DECLARE(GamepadAxis);
+	STRINGMAP_CLASS_DECLARE(GamepadButton);
+	STRINGMAP_CLASS_DECLARE(InputType);
 
 
 	static float clampval(float x);
 	static float clampval(float x);
 
 
-private:
-
-	static StringMap<Hat, HAT_MAX_ENUM>::Entry hatEntries[];
-	static StringMap<Hat, HAT_MAX_ENUM> hats;
-
-	static StringMap<GamepadAxis, GAMEPAD_AXIS_MAX_ENUM>::Entry gpAxisEntries[];
-	static StringMap<GamepadAxis, GAMEPAD_AXIS_MAX_ENUM> gpAxes;
-
-	static StringMap<GamepadButton, GAMEPAD_BUTTON_MAX_ENUM>::Entry gpButtonEntries[];
-	static StringMap<GamepadButton, GAMEPAD_BUTTON_MAX_ENUM> gpButtons;
-
-	static StringMap<InputType, INPUT_TYPE_MAX_ENUM>::Entry inputTypeEntries[];
-	static StringMap<InputType, INPUT_TYPE_MAX_ENUM> inputTypes;
-
 }; // Joystick
 }; // Joystick
 
 
 } // joystick
 } // joystick

+ 56 - 0
src/modules/joystick/sdl/Joystick.cpp

@@ -195,6 +195,30 @@ bool Joystick::isDown(const std::vector<int> &buttonlist) const
 	return false;
 	return false;
 }
 }
 
 
+void Joystick::setPlayerIndex(int index)
+{
+	if (!isConnected())
+		return;
+
+#if SDL_VERSION_ATLEAST(2, 0, 12)
+	SDL_JoystickSetPlayerIndex(joyhandle, index);
+#else
+	LOVE_UNUSED(index);
+#endif
+}
+
+int Joystick::getPlayerIndex() const
+{
+	if (!isConnected())
+		return -1;
+
+#if SDL_VERSION_ATLEAST(2, 0, 12)
+	return SDL_JoystickGetPlayerIndex(joyhandle);
+#else
+	return -1;
+#endif
+}
+
 bool Joystick::openGamepad(int deviceindex)
 bool Joystick::openGamepad(int deviceindex)
 {
 {
 	if (!SDL_IsGameController(deviceindex))
 	if (!SDL_IsGameController(deviceindex))
@@ -215,6 +239,30 @@ bool Joystick::isGamepad() const
 	return controller != nullptr;
 	return controller != nullptr;
 }
 }
 
 
+Joystick::GamepadType Joystick::getGamepadType() const
+{
+	if (controller == nullptr)
+		return GAMEPAD_TYPE_UNKNOWN;
+
+#if SDL_VERSION_ATLEAST(2, 0, 12)
+	switch (SDL_GameControllerGetType(controller))
+	{
+		case SDL_CONTROLLER_TYPE_UNKNOWN: return GAMEPAD_TYPE_UNKNOWN;
+		case SDL_CONTROLLER_TYPE_XBOX360: return GAMEPAD_TYPE_XBOX360;
+		case SDL_CONTROLLER_TYPE_XBOXONE: return GAMEPAD_TYPE_XBOXONE;
+		case SDL_CONTROLLER_TYPE_PS3: return GAMEPAD_TYPE_PS3;
+		case SDL_CONTROLLER_TYPE_PS4: return GAMEPAD_TYPE_PS4;
+		case SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO: return GAMEPAD_TYPE_NINTENDO_SWITCH_PRO;
+#if SDL_VERSION_ATLEAST(2, 0, 14)
+		case SDL_CONTROLLER_TYPE_VIRTUAL: return GAMEPAD_TYPE_VIRTUAL;
+		case SDL_CONTROLLER_TYPE_PS5: return GAMEPAD_TYPE_PS5;
+#endif
+	}
+#endif
+
+	return GAMEPAD_TYPE_UNKNOWN;
+}
+
 float Joystick::getGamepadAxis(love::joystick::Joystick::GamepadAxis axis) const
 float Joystick::getGamepadAxis(love::joystick::Joystick::GamepadAxis axis) const
 {
 {
 	if (!isConnected() || !isGamepad())
 	if (!isConnected() || !isGamepad())
@@ -636,6 +684,14 @@ EnumMap<Joystick::GamepadButton, SDL_GameControllerButton, Joystick::GAMEPAD_BUT
 	{Joystick::GAMEPAD_BUTTON_DPAD_DOWN, SDL_CONTROLLER_BUTTON_DPAD_DOWN},
 	{Joystick::GAMEPAD_BUTTON_DPAD_DOWN, SDL_CONTROLLER_BUTTON_DPAD_DOWN},
 	{Joystick::GAMEPAD_BUTTON_DPAD_LEFT, SDL_CONTROLLER_BUTTON_DPAD_LEFT},
 	{Joystick::GAMEPAD_BUTTON_DPAD_LEFT, SDL_CONTROLLER_BUTTON_DPAD_LEFT},
 	{Joystick::GAMEPAD_BUTTON_DPAD_RIGHT, SDL_CONTROLLER_BUTTON_DPAD_RIGHT},
 	{Joystick::GAMEPAD_BUTTON_DPAD_RIGHT, SDL_CONTROLLER_BUTTON_DPAD_RIGHT},
+#if SDL_VERSION_ATLEAST(2, 0, 14)
+	{Joystick::GAMEPAD_BUTTON_MISC1, SDL_CONTROLLER_BUTTON_MISC1},
+	{Joystick::GAMEPAD_BUTTON_PADDLE1, SDL_CONTROLLER_BUTTON_PADDLE1},
+	{Joystick::GAMEPAD_BUTTON_PADDLE2, SDL_CONTROLLER_BUTTON_PADDLE2},
+	{Joystick::GAMEPAD_BUTTON_PADDLE3, SDL_CONTROLLER_BUTTON_PADDLE3},
+	{Joystick::GAMEPAD_BUTTON_PADDLE4, SDL_CONTROLLER_BUTTON_PADDLE4},
+	{Joystick::GAMEPAD_BUTTON_TOUCHPAD, SDL_CONTROLLER_BUTTON_TOUCHPAD},
+#endif
 };
 };
 
 
 EnumMap<Joystick::GamepadButton, SDL_GameControllerButton, Joystick::GAMEPAD_BUTTON_MAX_ENUM> Joystick::gpButtons(Joystick::gpButtonEntries, sizeof(Joystick::gpButtonEntries));
 EnumMap<Joystick::GamepadButton, SDL_GameControllerButton, Joystick::GAMEPAD_BUTTON_MAX_ENUM> Joystick::gpButtons(Joystick::gpButtonEntries, sizeof(Joystick::gpButtonEntries));

+ 5 - 0
src/modules/joystick/sdl/Joystick.h

@@ -61,9 +61,14 @@ public:
 
 
 	bool isDown(const std::vector<int> &buttonlist) const override;
 	bool isDown(const std::vector<int> &buttonlist) const override;
 
 
+	void setPlayerIndex(int index) override;
+	int getPlayerIndex() const override;
+
 	bool openGamepad(int deviceindex) override;
 	bool openGamepad(int deviceindex) override;
 	bool isGamepad() const override;
 	bool isGamepad() const override;
 
 
+	GamepadType getGamepadType() const override;
+
 	float getGamepadAxis(GamepadAxis axis) const override;
 	float getGamepadAxis(GamepadAxis axis) const override;
 	bool isGamepadDown(const std::vector<GamepadButton> &blist) const override;
 	bool isGamepadDown(const std::vector<GamepadButton> &blist) const override;
 
 

+ 28 - 0
src/modules/joystick/wrap_Joystick.cpp

@@ -171,6 +171,22 @@ int w_Joystick_isDown(lua_State *L)
 	return 1;
 	return 1;
 }
 }
 
 
+int w_Joystick_setPlayerIndex(lua_State *L)
+{
+	Joystick *j = luax_checkjoystick(L, 1);
+	int index = (int) luaL_checkinteger(L, 2) - 1;
+	j->setPlayerIndex(index);
+	return 0;
+}
+
+int w_Joystick_getPlayerIndex(lua_State *L)
+{
+	Joystick *j = luax_checkjoystick(L, 1);
+	int index = j->getPlayerIndex();
+	lua_pushinteger(L, index >= 0 ? index + 1 : index);
+	return 1;
+}
+
 int w_Joystick_isGamepad(lua_State *L)
 int w_Joystick_isGamepad(lua_State *L)
 {
 {
 	Joystick *j = luax_checkjoystick(L, 1);
 	Joystick *j = luax_checkjoystick(L, 1);
@@ -178,6 +194,15 @@ int w_Joystick_isGamepad(lua_State *L)
 	return 1;
 	return 1;
 }
 }
 
 
+int w_Joystick_getGamepadType(lua_State *L)
+{
+	Joystick *j = luax_checkjoystick(L, 1);
+	const char *str = "unknown";
+	Joystick::getConstant(j->getGamepadType(), str);
+	lua_pushstring(L, str);
+	return 1;
+}
+
 int w_Joystick_getGamepadAxis(lua_State *L)
 int w_Joystick_getGamepadAxis(lua_State *L)
 {
 {
 	Joystick *j = luax_checkjoystick(L, 1);
 	Joystick *j = luax_checkjoystick(L, 1);
@@ -357,8 +382,11 @@ static const luaL_Reg w_Joystick_functions[] =
 	{ "getAxes", w_Joystick_getAxes },
 	{ "getAxes", w_Joystick_getAxes },
 	{ "getHat", w_Joystick_getHat },
 	{ "getHat", w_Joystick_getHat },
 	{ "isDown", w_Joystick_isDown },
 	{ "isDown", w_Joystick_isDown },
+	{ "setPlayerIndex", w_Joystick_setPlayerIndex },
+	{ "getPlayerIndex", w_Joystick_getPlayerIndex },
 
 
 	{ "isGamepad", w_Joystick_isGamepad },
 	{ "isGamepad", w_Joystick_isGamepad },
+	{ "getGamepadType", w_Joystick_getGamepadType },
 	{ "getGamepadAxis", w_Joystick_getGamepadAxis },
 	{ "getGamepadAxis", w_Joystick_getGamepadAxis },
 	{ "isGamepadDown", w_Joystick_isGamepadDown },
 	{ "isGamepadDown", w_Joystick_isGamepadDown },
 	{ "getGamepadMapping", w_Joystick_getGamepadMapping },
 	{ "getGamepadMapping", w_Joystick_getGamepadMapping },