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

This commit is contained in:
niki
2022-06-11 01:22:29 +02:00
55 changed files with 2101 additions and 541 deletions
+6
View File
@@ -515,6 +515,8 @@ set(LOVE_SRC_MODULE_GRAPHICS_ROOT
src/modules/graphics/Font.h
src/modules/graphics/Graphics.cpp
src/modules/graphics/Graphics.h
src/modules/graphics/GraphicsReadback.cpp
src/modules/graphics/GraphicsReadback.h
src/modules/graphics/Mesh.cpp
src/modules/graphics/Mesh.h
src/modules/graphics/ParticleSystem.cpp
@@ -550,6 +552,8 @@ set(LOVE_SRC_MODULE_GRAPHICS_ROOT
src/modules/graphics/wrap_Font.h
src/modules/graphics/wrap_Graphics.cpp
src/modules/graphics/wrap_Graphics.h
src/modules/graphics/wrap_GraphicsReadback.cpp
src/modules/graphics/wrap_GraphicsReadback.h
src/modules/graphics/wrap_Mesh.cpp
src/modules/graphics/wrap_Mesh.h
src/modules/graphics/wrap_ParticleSystem.cpp
@@ -575,6 +579,8 @@ set(LOVE_SRC_MODULE_GRAPHICS_OPENGL
src/modules/graphics/opengl/FenceSync.h
src/modules/graphics/opengl/Graphics.cpp
src/modules/graphics/opengl/Graphics.h
src/modules/graphics/opengl/GraphicsReadback.cpp
src/modules/graphics/opengl/GraphicsReadback.h
src/modules/graphics/opengl/OpenGL.cpp
src/modules/graphics/opengl/OpenGL.h
src/modules/graphics/opengl/Shader.cpp
+1 -1
View File
@@ -88,7 +88,7 @@ This distribution contains code from the following projects (full license text b
- dr_flac
Website: https://github.com/mackron/dr_libs
Source download: https://github.com/mackron/dr_libs/blob/c5e5355/dr_flac.h
Source download: https://github.com/mackron/dr_libs/blob/15f37e3/dr_flac.h
License: MIT/Expat
Copyright 2018 David Reid
@@ -823,6 +823,8 @@
FA6A2B7A1F60B8250074C308 /* wrap_ByteData.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA6A2B781F60B8250074C308 /* wrap_ByteData.cpp */; };
FA6A2B7B1F60B8250074C308 /* wrap_ByteData.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA6A2B781F60B8250074C308 /* wrap_ByteData.cpp */; };
FA6BDE5C1F31725300786805 /* Color.h in Headers */ = {isa = PBXBuildFile; fileRef = FA6BDE5B1F31725300786805 /* Color.h */; };
FA6BDF89280B62A000240F2A /* GraphicsReadback.mm in Sources */ = {isa = PBXBuildFile; fileRef = FA6BDF88280B62A000240F2A /* GraphicsReadback.mm */; };
FA6BDF8A280B62A000240F2A /* GraphicsReadback.mm in Sources */ = {isa = PBXBuildFile; fileRef = FA6BDF88280B62A000240F2A /* GraphicsReadback.mm */; };
FA6BDF8E281219E900240F2A /* DataStream.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA6BDF8C281219E900240F2A /* DataStream.cpp */; };
FA6BDF8F281219E900240F2A /* DataStream.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA6BDF8C281219E900240F2A /* DataStream.cpp */; };
FA6BDF90281219E900240F2A /* DataStream.h in Headers */ = {isa = PBXBuildFile; fileRef = FA6BDF8D281219E900240F2A /* DataStream.h */; };
@@ -832,6 +834,14 @@
FA7E9207277E120900C24CB2 /* theora.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA7E9206277E120900C24CB2 /* theora.xcframework */; };
FA84DE612778D7F3002674C6 /* SpirvIntrinsics.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA84DE602778D7F3002674C6 /* SpirvIntrinsics.cpp */; };
FA84DE622778D7F3002674C6 /* SpirvIntrinsics.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA84DE602778D7F3002674C6 /* SpirvIntrinsics.cpp */; };
FA84DE6627791C36002674C6 /* GraphicsReadback.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA84DE6427791C36002674C6 /* GraphicsReadback.cpp */; };
FA84DE6727791C36002674C6 /* GraphicsReadback.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA84DE6427791C36002674C6 /* GraphicsReadback.cpp */; };
FA84DE6827791C36002674C6 /* GraphicsReadback.h in Headers */ = {isa = PBXBuildFile; fileRef = FA84DE6527791C36002674C6 /* GraphicsReadback.h */; };
FA84DE6B277943F6002674C6 /* GraphicsReadback.h in Headers */ = {isa = PBXBuildFile; fileRef = FA84DE69277943F6002674C6 /* GraphicsReadback.h */; };
FA84DE6C277943F6002674C6 /* GraphicsReadback.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA84DE6A277943F6002674C6 /* GraphicsReadback.cpp */; };
FA84DE6D277943F6002674C6 /* GraphicsReadback.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA84DE6A277943F6002674C6 /* GraphicsReadback.cpp */; };
FA84DE7127795E22002674C6 /* wrap_GraphicsReadback.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA84DE6F27795E22002674C6 /* wrap_GraphicsReadback.cpp */; };
FA84DE7227795E22002674C6 /* wrap_GraphicsReadback.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA84DE6F27795E22002674C6 /* wrap_GraphicsReadback.cpp */; };
FA84DE76277CB3D5002674C6 /* SDL2.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA84DE75277CB3D4002674C6 /* SDL2.xcframework */; };
FA84DE7A277D4C88002674C6 /* modplug.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA84DE79277D4C88002674C6 /* modplug.xcframework */; };
FA84DE7C277E045E002674C6 /* ogg.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA84DE7B277E045E002674C6 /* ogg.xcframework */; };
@@ -1907,6 +1917,8 @@
FA6A2B771F60B8250074C308 /* wrap_ByteData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wrap_ByteData.h; sourceTree = "<group>"; };
FA6A2B781F60B8250074C308 /* wrap_ByteData.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = wrap_ByteData.cpp; sourceTree = "<group>"; };
FA6BDE5B1F31725300786805 /* Color.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Color.h; sourceTree = "<group>"; };
FA6BDF88280B62A000240F2A /* GraphicsReadback.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = GraphicsReadback.mm; sourceTree = "<group>"; };
FA6BDF8B280B62B600240F2A /* GraphicsReadback.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GraphicsReadback.h; sourceTree = "<group>"; };
FA6BDF8C281219E900240F2A /* DataStream.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DataStream.cpp; sourceTree = "<group>"; };
FA6BDF8D281219E900240F2A /* DataStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DataStream.h; sourceTree = "<group>"; };
FA7634481E28722A0066EF9E /* StreamBuffer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StreamBuffer.cpp; sourceTree = "<group>"; };
@@ -1917,6 +1929,12 @@
FA84DE5E2778D7DC002674C6 /* glslang_c_interface.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = glslang_c_interface.h; sourceTree = "<group>"; };
FA84DE5F2778D7DC002674C6 /* glslang_c_shader_types.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = glslang_c_shader_types.h; sourceTree = "<group>"; };
FA84DE602778D7F3002674C6 /* SpirvIntrinsics.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SpirvIntrinsics.cpp; sourceTree = "<group>"; };
FA84DE6427791C36002674C6 /* GraphicsReadback.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = GraphicsReadback.cpp; sourceTree = "<group>"; };
FA84DE6527791C36002674C6 /* GraphicsReadback.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GraphicsReadback.h; sourceTree = "<group>"; };
FA84DE69277943F6002674C6 /* GraphicsReadback.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GraphicsReadback.h; sourceTree = "<group>"; };
FA84DE6A277943F6002674C6 /* GraphicsReadback.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = GraphicsReadback.cpp; sourceTree = "<group>"; };
FA84DE6E27795E22002674C6 /* wrap_GraphicsReadback.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wrap_GraphicsReadback.h; sourceTree = "<group>"; };
FA84DE6F27795E22002674C6 /* wrap_GraphicsReadback.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = wrap_GraphicsReadback.cpp; sourceTree = "<group>"; };
FA84DE75277CB3D4002674C6 /* SDL2.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = SDL2.xcframework; path = ios/libraries/SDL2.xcframework; sourceTree = "<group>"; };
FA84DE79277D4C88002674C6 /* modplug.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = modplug.xcframework; path = ios/libraries/modplug.xcframework; sourceTree = "<group>"; };
FA84DE7B277E045E002674C6 /* ogg.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = ogg.xcframework; path = ios/libraries/ogg.xcframework; sourceTree = "<group>"; };
@@ -2819,6 +2837,8 @@
FA1BA09C1E16CFCE00AA2803 /* Font.h */,
FA0B7B8A1A95902C000E1D17 /* Graphics.cpp */,
FA0B7B8B1A95902C000E1D17 /* Graphics.h */,
FA84DE6427791C36002674C6 /* GraphicsReadback.cpp */,
FA84DE6527791C36002674C6 /* GraphicsReadback.h */,
FADF54231E3DA5BA00012CC0 /* Mesh.cpp */,
FADF54241E3DA5BA00012CC0 /* Mesh.h */,
FA18CECC23DBC6E000263725 /* metal */,
@@ -2857,6 +2877,8 @@
FADF54391E3DAFF700012CC0 /* wrap_Graphics.cpp */,
FADF543A1E3DAFF700012CC0 /* wrap_Graphics.h */,
FADF54371E3DAFBA00012CC0 /* wrap_Graphics.lua */,
FA84DE6F27795E22002674C6 /* wrap_GraphicsReadback.cpp */,
FA84DE6E27795E22002674C6 /* wrap_GraphicsReadback.h */,
FADF54281E3DAADA00012CC0 /* wrap_Mesh.cpp */,
FADF54291E3DAADA00012CC0 /* wrap_Mesh.h */,
FADF541E1E3DA52C00012CC0 /* wrap_ParticleSystem.cpp */,
@@ -2887,6 +2909,8 @@
FA28EBD41E352DB5003446F4 /* FenceSync.h */,
FA0B7B911A95902C000E1D17 /* Graphics.cpp */,
FA0B7B921A95902C000E1D17 /* Graphics.h */,
FA84DE6A277943F6002674C6 /* GraphicsReadback.cpp */,
FA84DE69277943F6002674C6 /* GraphicsReadback.h */,
FA0B7B971A95902C000E1D17 /* OpenGL.cpp */,
FA0B7B981A95902C000E1D17 /* OpenGL.h */,
FA0B7B9D1A95902C000E1D17 /* Shader.cpp */,
@@ -3354,6 +3378,8 @@
FA18CEE823DBC8D400263725 /* Buffer.mm */,
FA18CED523DBC6E000263725 /* Graphics.h */,
FA18CED323DBC6E000263725 /* Graphics.mm */,
FA6BDF8B280B62B600240F2A /* GraphicsReadback.h */,
FA6BDF88280B62A000240F2A /* GraphicsReadback.mm */,
FA18CED423DBC6E000263725 /* Metal.h */,
FA18CED023DBC6E000263725 /* Metal.mm */,
FA18CECD23DBC6E000263725 /* Shader.h */,
@@ -4338,6 +4364,7 @@
FABDA9C92552448300B5C523 /* b2_distance.h in Headers */,
FA24348921D401CB00B8918A /* pch.h in Headers */,
FAF140991E20934C00F898D2 /* PpTokens.h in Headers */,
FA84DE6827791C36002674C6 /* GraphicsReadback.h in Headers */,
FAF1405B1E20934C00F898D2 /* InfoSink.h in Headers */,
FA18CF2423DCF67900263725 /* spirv_cpp.hpp in Headers */,
FA0B7B321A958EA3000E1D17 /* wuff.h in Headers */,
@@ -4394,6 +4421,7 @@
FAC7CD961FE755B4006A60C7 /* lz4opt.h in Headers */,
FA9D8DD31DEB56C3002CD881 /* pixelformat.h in Headers */,
FABDA9DE2552448300B5C523 /* b2_prismatic_joint.h in Headers */,
FA84DE6B277943F6002674C6 /* GraphicsReadback.h in Headers */,
FAF140A61E20934C00F898D2 /* ScanContext.h in Headers */,
FABDA97B2552448200B5C523 /* b2_chain_polygon_contact.h in Headers */,
FA0B7E4A1A95902C000E1D17 /* wrap_DistanceJoint.h in Headers */,
@@ -4468,7 +4496,7 @@
08FB7793FE84155DC02AAC07 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1310;
LastUpgradeCheck = 1340;
TargetAttributes = {
FA0B78DC1A958B90000E1D17 = {
CreatedOnToolsVersion = 6.1.1;
@@ -4548,6 +4576,7 @@
FA18CF4723DD1A8100263725 /* ShaderStage.mm in Sources */,
FA0B7ECC1A95902C000E1D17 /* wrap_Channel.cpp in Sources */,
FA0B7E6D1A95902C000E1D17 /* wrap_RevoluteJoint.cpp in Sources */,
FA84DE7227795E22002674C6 /* wrap_GraphicsReadback.cpp in Sources */,
FACA02FA1F5E397B0084B28F /* DataModule.cpp in Sources */,
FA0B7E641A95902C000E1D17 /* wrap_PolygonShape.cpp in Sources */,
FA4F2C031DE936C200CA37D7 /* auxiliar.c in Sources */,
@@ -4619,6 +4648,7 @@
FADF53FE1E3D74F200012CC0 /* Text.cpp in Sources */,
FA0B7D191A95902C000E1D17 /* TrueTypeRasterizer.cpp in Sources */,
FAC271E723B5B5B400C200D3 /* renderstate.cpp in Sources */,
FA84DE6727791C36002674C6 /* GraphicsReadback.cpp in Sources */,
FA0B7CFB1A95902C000E1D17 /* Filesystem.cpp in Sources */,
FABDA9F82552448300B5C523 /* b2_dynamic_tree.cpp in Sources */,
FABDA98B2552448300B5C523 /* b2_revolute_joint.cpp in Sources */,
@@ -4651,6 +4681,7 @@
FABDA9A32552448300B5C523 /* b2_friction_joint.cpp in Sources */,
FAF140AD1E20934C00F898D2 /* Versions.cpp in Sources */,
FA0B793C1A958E3B000E1D17 /* runtime.cpp in Sources */,
FA6BDF8A280B62A000240F2A /* GraphicsReadback.mm in Sources */,
FAF140DC1E20934C00F898D2 /* InitializeDll.cpp in Sources */,
FA0B7DBC1A95902C000E1D17 /* Joystick.cpp in Sources */,
FA0B7DAF1A95902C000E1D17 /* wrap_CompressedImageData.cpp in Sources */,
@@ -4766,6 +4797,7 @@
FA0B7CF81A95902C000E1D17 /* FileData.cpp in Sources */,
FA0B7DA61A95902C000E1D17 /* PNGHandler.cpp in Sources */,
FAF6C9F523C2DE2900D7B5BC /* Logger.cpp in Sources */,
FA84DE6D277943F6002674C6 /* GraphicsReadback.cpp in Sources */,
FAE64A932071365100BC7981 /* physfs_platform_haiku.cpp in Sources */,
FA0B7E981A95902C000E1D17 /* Sound.cpp in Sources */,
FA0B7E371A95902C000E1D17 /* WheelJoint.cpp in Sources */,
@@ -4969,6 +5001,7 @@
FAF140A71E20934C00F898D2 /* ShaderLang.cpp in Sources */,
FAC271E623B5B5B400C200D3 /* renderstate.cpp in Sources */,
FA1BA09D1E16CFCE00AA2803 /* Font.cpp in Sources */,
FA84DE7127795E22002674C6 /* wrap_GraphicsReadback.cpp in Sources */,
FA0B7E6C1A95902C000E1D17 /* wrap_RevoluteJoint.cpp in Sources */,
FA0B7E631A95902C000E1D17 /* wrap_PolygonShape.cpp in Sources */,
FAC7CD7B1FE35E95006A60C7 /* physfs_platform_unix.c in Sources */,
@@ -5039,6 +5072,7 @@
FADF54341E3DAE6E00012CC0 /* wrap_SpriteBatch.cpp in Sources */,
FA0B7E0C1A95902C000E1D17 /* Fixture.cpp in Sources */,
FA0B7D181A95902C000E1D17 /* TrueTypeRasterizer.cpp in Sources */,
FA84DE6627791C36002674C6 /* GraphicsReadback.cpp in Sources */,
FA0B7CFA1A95902C000E1D17 /* Filesystem.cpp in Sources */,
FABDA9F72552448300B5C523 /* b2_dynamic_tree.cpp in Sources */,
FABDA98A2552448300B5C523 /* b2_revolute_joint.cpp in Sources */,
@@ -5071,6 +5105,7 @@
FABDA9A22552448300B5C523 /* b2_friction_joint.cpp in Sources */,
217DFC0D1D9F6D490055D849 /* unixudp.c in Sources */,
FA0B7CDC1A95902C000E1D17 /* Source.cpp in Sources */,
FA6BDF89280B62A000240F2A /* GraphicsReadback.mm in Sources */,
FA0B7DC41A95902C000E1D17 /* wrap_JoystickModule.cpp in Sources */,
FA0B7E6F1A95902C000E1D17 /* wrap_RopeJoint.cpp in Sources */,
FA24348721D401CB00B8918A /* attribute.cpp in Sources */,
@@ -5190,6 +5225,7 @@
FA0B7DA51A95902C000E1D17 /* PNGHandler.cpp in Sources */,
FA0B7B371A958EA3000E1D17 /* wuff_internal.c in Sources */,
FA0B7E971A95902C000E1D17 /* Sound.cpp in Sources */,
FA84DE6C277943F6002674C6 /* GraphicsReadback.cpp in Sources */,
FA4F2B791DE0125B00CA37D7 /* xxhash.c in Sources */,
FA0B7E361A95902C000E1D17 /* WheelJoint.cpp in Sources */,
FADF542F1E3DABF600012CC0 /* SpriteBatch.cpp in Sources */,
@@ -328,7 +328,7 @@
29B97313FDCFA39411CA2CEA /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1310;
LastUpgradeCheck = 1340;
TargetAttributes = {
FA0B7F051A95AAF3000E1D17 = {
CreatedOnToolsVersion = 6.1.1;
+194 -134
View File
@@ -19,6 +19,7 @@
**/
#include "android.h"
#include "Object.h"
#ifdef LOVE_ANDROID
@@ -45,13 +46,11 @@ namespace android
void setImmersive(bool immersive_active)
{
JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv();
jobject activity = (jobject) SDL_AndroidGetActivity();
jclass clazz = env->GetObjectClass(activity);
jclass clazz(env->GetObjectClass(activity));
jmethodID method_id = env->GetMethodID(clazz, "setImmersiveMode", "(Z)V");
env->CallVoidMethod(activity, method_id, immersive_active);
static jmethodID setImmersiveMethod = env->GetMethodID(clazz, "setImmersiveMode", "(Z)V");
env->CallVoidMethod(activity, setImmersiveMethod, immersive_active);
env->DeleteLocalRef(activity);
env->DeleteLocalRef(clazz);
@@ -60,18 +59,16 @@ void setImmersive(bool immersive_active)
bool getImmersive()
{
JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv();
jobject activity = (jobject) SDL_AndroidGetActivity();
jclass clazz = env->GetObjectClass(activity);
jclass clazz(env->GetObjectClass(activity));
jmethodID method_id = env->GetMethodID(clazz, "getImmersiveMode", "()Z");
jboolean immersive_active = env->CallBooleanMethod(activity, method_id);
static jmethodID getImmersiveMethod = env->GetMethodID(clazz, "getImmersiveMode", "()Z");
jboolean immersiveActive = env->CallBooleanMethod(activity, getImmersiveMethod);
env->DeleteLocalRef(activity);
env->DeleteLocalRef(clazz);
return immersive_active;
return immersiveActive;
}
double getScreenScale()
@@ -81,16 +78,13 @@ double getScreenScale()
if (result == -1.)
{
JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv();
jclass activity = env->FindClass("org/love2d/android/GameActivity");
jobject activity = (jobject) SDL_AndroidGetActivity();
jclass clazz = env->GetObjectClass(activity);
jmethodID getMetrics = env->GetStaticMethodID(activity, "getMetrics", "()Landroid/util/DisplayMetrics;");
jobject metrics = env->CallStaticObjectMethod(activity, getMetrics);
jclass metricsClass = env->GetObjectClass(metrics);
jmethodID getDPIMethod = env->GetMethodID(clazz, "getDPIScale", "()F");
result = (double) env->CallFloatMethod(activity, getDPIMethod);
result = env->GetFloatField(metrics, env->GetFieldID(metricsClass, "density", "F"));
env->DeleteLocalRef(metricsClass);
env->DeleteLocalRef(metrics);
env->DeleteLocalRef(clazz);
env->DeleteLocalRef(activity);
}
@@ -101,8 +95,10 @@ bool getSafeArea(int &top, int &left, int &bottom, int &right)
{
JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv();
jobject activity = (jobject) SDL_AndroidGetActivity();
jclass clazz(env->GetObjectClass(activity));
jmethodID methodID = env->GetMethodID(clazz, "initializeSafeArea", "()Z");
jclass clazz = env->GetObjectClass(activity);
static jmethodID methodID = env->GetMethodID(clazz, "getSafeArea", "()Z");
bool hasSafeArea = false;
if (methodID == nullptr)
@@ -122,64 +118,33 @@ bool getSafeArea(int &top, int &left, int &bottom, int &right)
return hasSafeArea;
}
const char *getSelectedGameFile()
{
static const char *path = NULL;
if (path)
{
delete path;
path = NULL;
}
JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv();
jclass activity = env->FindClass("org/love2d/android/GameActivity");
jmethodID getGamePath = env->GetStaticMethodID(activity, "getGamePath", "()Ljava/lang/String;");
jstring gamePath = (jstring) env->CallStaticObjectMethod(activity, getGamePath);
const char *utf = env->GetStringUTFChars(gamePath, 0);
if (utf)
{
path = SDL_strdup(utf);
env->ReleaseStringUTFChars(gamePath, utf);
}
env->DeleteLocalRef(gamePath);
env->DeleteLocalRef(activity);
return path;
}
bool openURL(const std::string &url)
{
JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv();
jclass activity = env->FindClass("org/love2d/android/GameActivity");
jobject activity = (jobject) SDL_AndroidGetActivity();
jclass clazz = env->GetObjectClass(activity);
jmethodID openURL = env->GetStaticMethodID(activity, "openURLFromLOVE", "(Ljava/lang/String;)Z");
static jmethodID openURL = env->GetMethodID(clazz, "openURLFromLOVE", "(Ljava/lang/String;)Z");
if (openURL == nullptr)
{
env->ExceptionClear();
openURL = env->GetStaticMethodID(activity, "openURL", "(Ljava/lang/String;)Z");
}
jstring jstringURL = env->NewStringUTF(url.c_str());
jboolean result = env->CallBooleanMethod(clazz, openURL, jstringURL);
jstring url_jstring = (jstring) env->NewStringUTF(url.c_str());
jboolean result = env->CallStaticBooleanMethod(activity, openURL, url_jstring);
env->DeleteLocalRef(url_jstring);
env->DeleteLocalRef(jstringURL);
env->DeleteLocalRef(clazz);
env->DeleteLocalRef(activity);
return result;
return (bool) result;
}
void vibrate(double seconds)
{
JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv();
jclass activity = env->FindClass("org/love2d/android/GameActivity");
jobject activity = (jobject) SDL_AndroidGetActivity();
jclass clazz = env->GetObjectClass(activity);
jmethodID vibrate_method = env->GetStaticMethodID(activity, "vibrate", "(D)V");
env->CallStaticVoidMethod(activity, vibrate_method, seconds);
static jmethodID vibrateMethod = env->GetMethodID(clazz, "vibrate", "(D)V");
env->CallVoidMethod(activity, vibrateMethod, seconds);
env->DeleteLocalRef(clazz);
env->DeleteLocalRef(activity);
}
@@ -192,40 +157,9 @@ void freeGameArchiveMemory(void *ptr)
delete[] game_love_data;
}
bool loadGameArchiveToMemory(const char* filename, char **ptr, size_t *size)
{
SDL_RWops *asset_game_file = SDL_RWFromFile(filename, "rb");
if (!asset_game_file) {
SDL_Log("Could not find %s", filename);
return false;
}
Sint64 file_size = asset_game_file->size(asset_game_file);
if (file_size <= 0) {
SDL_Log("Could not load game from %s. File has invalid file size: %d.", filename, (int) file_size);
return false;
}
*ptr = new char[file_size];
if (!*ptr) {
SDL_Log("Could not allocate memory for in-memory game archive");
return false;
}
size_t bytes_copied = asset_game_file->read(asset_game_file, (void*) *ptr, sizeof(char), (size_t) file_size);
if (bytes_copied != file_size) {
SDL_Log("Incomplete copy of in-memory game archive!");
delete[] *ptr;
return false;
}
*size = (size_t) file_size;
return true;
}
bool directoryExists(const char *path)
{
struct stat s;
struct stat s {};
int err = stat(path, &s);
if (err == -1)
{
@@ -284,9 +218,9 @@ bool hasRecordingPermission()
{
JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv();
jobject activity = (jobject) SDL_AndroidGetActivity();
jclass clazz = env->GetObjectClass(activity);
jclass clazz(env->GetObjectClass(activity));
jmethodID methodID = env->GetMethodID(clazz, "hasRecordAudioPermission", "()Z");
static jmethodID methodID = env->GetMethodID(clazz, "hasRecordAudioPermission", "()Z");
jboolean result = false;
if (methodID == nullptr)
@@ -755,21 +689,11 @@ void deinitializeVirtualArchive()
bool checkFusedGame(void **physfsIO_Out)
{
// TODO: Reorder the loading in 12.0
PHYSFS_Io *&io = *(PHYSFS_Io **) physfsIO_Out;
AAssetManager *assetManager = getAssetManager();
// Prefer game.love inside assets/ folder
AAsset *asset = AAssetManager_open(assetManager, "game.love", AASSET_MODE_RANDOM);
if (asset)
{
io = aasset::io::fromAAsset(assetManager, "game.love", asset);
return true;
}
// If there's no game.love inside assets/ try main.lua
asset = AAssetManager_open(assetManager, "main.lua", AASSET_MODE_STREAMING);
// Prefer main.lua inside assets/ folder
AAsset *asset = AAssetManager_open(assetManager, "main.lua", AASSET_MODE_STREAMING);
if (asset)
{
AAsset_close(asset);
@@ -777,6 +701,14 @@ bool checkFusedGame(void **physfsIO_Out)
return true;
}
// If there's no main.lua inside assets/ try game.love
asset = AAssetManager_open(assetManager, "game.love", AASSET_MODE_RANDOM);
if (asset)
{
io = aasset::io::fromAAsset(assetManager, "game.love", asset);
return true;
}
// Not found
return false;
}
@@ -784,43 +716,171 @@ bool checkFusedGame(void **physfsIO_Out)
const char *getCRequirePath()
{
static bool initialized = false;
static const char *path = nullptr;
static std::string path;
if (!initialized)
{
JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv();
jobject activity = (jobject) SDL_AndroidGetActivity();
jclass clazz = env->GetObjectClass(activity);
jclass clazz(env->GetObjectClass(activity));
jmethodID method_id = env->GetMethodID(clazz, "getCRequirePath", "()Ljava/lang/String;");
static jmethodID getCRequireMethod = env->GetMethodID(clazz, "getCRequirePath", "()Ljava/lang/String;");
path = "";
initialized = true;
if (method_id)
jstring cpath = (jstring) env->CallObjectMethod(activity, getCRequireMethod);
const char *utf = env->GetStringUTFChars(cpath, nullptr);
if (utf)
{
jstring cpath = (jstring) env->CallObjectMethod(activity, method_id);
const char *utf = env->GetStringUTFChars(cpath, nullptr);
if (utf)
{
path = SDL_strdup(utf);
env->ReleaseStringUTFChars(cpath, utf);
}
env->DeleteLocalRef(cpath);
}
else
{
// NoSuchMethodException is thrown in case methodID is null
env->ExceptionClear();
return "";
path = utf;
env->ReleaseStringUTFChars(cpath, utf);
}
env->DeleteLocalRef(cpath);
env->DeleteLocalRef(activity);
env->DeleteLocalRef(clazz);
}
return path;
return path.c_str();
}
int getFDFromContentProtocol(const char *path)
{
int fd = -1;
if (strstr(path, "content://") == path)
{
JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv();
jobject activity = (jobject) SDL_AndroidGetActivity();
jclass clazz = env->GetObjectClass(activity);
static jmethodID converter = env->GetMethodID(clazz, "convertToFileDescriptor", "(Ljava/lang/String;)I");
jstring uri = env->NewStringUTF(path);
fd = (int) env->CallIntMethod(activity, converter, uri);
env->DeleteLocalRef(uri);
env->DeleteLocalRef(clazz);
env->DeleteLocalRef(activity);
}
return fd;
}
int getFDFromLoveProtocol(const char *path)
{
constexpr const char PROTOCOL[] = "love2d://fd/";
constexpr size_t PROTOCOL_LEN = sizeof(PROTOCOL) - 1;
if (*path == '/')
path++;
if (memcmp(path, PROTOCOL, PROTOCOL_LEN) == 0)
{
try
{
return std::stoi(path + PROTOCOL_LEN, nullptr, 10);
}
catch (std::logic_error &)
{ }
}
return -1;
}
class FileDescriptorTracker: public love::Object
{
public:
explicit FileDescriptorTracker(int fd): Object(), fd(fd) {}
~FileDescriptorTracker() override { close(fd); }
int getFd() const { return fd; }
private:
int fd;
};
struct FileDescriptorIO
{
FileDescriptorTracker *fd;
off64_t size;
off64_t offset;
};
void *getIOFromFD(int fd)
{
if (fd == -1)
return nullptr;
// Create file descriptor IO structure
FileDescriptorIO *fdio = new FileDescriptorIO();
fdio->size = lseek64(fd, 0, SEEK_END);
fdio->offset = 0;
lseek64(fd, 0, SEEK_SET);
if (fdio->size == -1)
{
// Cannot get size
delete fdio;
return nullptr;
}
fdio->fd = new FileDescriptorTracker(fd);
PHYSFS_Io *io = new PHYSFS_Io();
io->version = 0;
io->opaque = fdio;
io->read = [](PHYSFS_Io *io, void *buf, PHYSFS_uint64 size)
{
FileDescriptorIO *fdio = (FileDescriptorIO *) io->opaque;
ssize_t ret = pread64(fdio->fd->getFd(), buf, (size_t) size, fdio->offset);
if (ret == -1)
PHYSFS_setErrorCode(PHYSFS_ERR_OTHER_ERROR);
else
fdio->offset = std::min(fdio->offset + (off64_t) ret, fdio->size);
return (PHYSFS_sint64) ret;
};
io->write = nullptr;
io->seek = [](PHYSFS_Io *io, PHYSFS_uint64 offset)
{
FileDescriptorIO *fdio = (FileDescriptorIO *) io->opaque;
fdio->offset = std::min(std::max<off64_t>((off64_t) offset, 0), fdio->size);
// Always success
return 1;
};
io->tell = [](PHYSFS_Io *io)
{
FileDescriptorIO *fdio = (FileDescriptorIO *) io->opaque;
return (PHYSFS_sint64) fdio->offset;
};
io->length = [](PHYSFS_Io *io)
{
FileDescriptorIO *fdio = (FileDescriptorIO *) io->opaque;
return (PHYSFS_sint64) fdio->size;
};
io->duplicate = [](PHYSFS_Io *io)
{
FileDescriptorIO *fdio = (FileDescriptorIO *) io->opaque;
FileDescriptorIO *fdio2 = new FileDescriptorIO();
PHYSFS_Io *io2 = new PHYSFS_Io();
fdio->fd->retain();
// Copy data
*fdio2 = *fdio;
*io2 = *io;
io2->opaque = fdio2;
return io2;
};
io->flush = nullptr;
io->destroy = [](PHYSFS_Io *io)
{
FileDescriptorIO *fdio = (FileDescriptorIO *) io->opaque;
fdio->fd->release();
delete fdio;
delete io;
};
return io;
}
} // android
+21 -7
View File
@@ -50,11 +50,6 @@ double getScreenScale();
**/
bool getSafeArea(int &top, int &left, int &bottom, int &right);
/**
* Gets the selected love file in the device filesystem.
**/
const char *getSelectedGameFile();
bool openURL(const std::string &url);
void vibrate(double seconds);
@@ -64,8 +59,6 @@ void vibrate(double seconds);
*/
void freeGameArchiveMemory(void *ptr);
bool loadGameArchiveToMemory(const char *filename, char **ptr, size_t *size);
bool directoryExists(const char *path);
bool mkdir(const char *path);
@@ -103,6 +96,27 @@ bool checkFusedGame(void **physfsIO_Out);
const char *getCRequirePath();
/**
* Convert "content://" to file descriptor.
* @param path Path with content:// URI
* @return File descriptor if successful, -1 on failure.
*/
int getFDFromContentProtocol(const char *path);
/**
* Attempt to parse "(/)love2d://fd/<fd>" from path.
* @param path Potentially special path.
* @return File descriptor passed if successful, -1 if path is not valid.
*/
int getFDFromLoveProtocol(const char *path);
/**
* Create PHYSFS_Io from file descriptor.
* @param fd File descriptor
* @return PHYSFS_Io casted to void*.
*/
void *getIOFromFD(int fd);
} // android
} // love
+171 -77
View File
@@ -1,6 +1,6 @@
/*
FLAC audio decoder. Choice of public domain or MIT-0. See license statements at the end of this file.
dr_flac - v0.12.33 - 2021-12-22
dr_flac - v0.12.38 - 2022-04-10
David Reid - mackron@gmail.com
@@ -232,7 +232,7 @@ extern "C" {
#define DRFLAC_VERSION_MAJOR 0
#define DRFLAC_VERSION_MINOR 12
#define DRFLAC_VERSION_REVISION 33
#define DRFLAC_VERSION_REVISION 38
#define DRFLAC_VERSION_STRING DRFLAC_XSTRINGIFY(DRFLAC_VERSION_MAJOR) "." DRFLAC_XSTRINGIFY(DRFLAC_VERSION_MINOR) "." DRFLAC_XSTRINGIFY(DRFLAC_VERSION_REVISION)
#include <stddef.h> /* For size_t. */
@@ -1363,9 +1363,15 @@ DRFLAC_API drflac_bool32 drflac_next_cuesheet_track(drflac_cuesheet_track_iterat
I am using "__inline__" only when we're compiling in strict ANSI mode.
*/
#if defined(__STRICT_ANSI__)
#define DRFLAC_INLINE __inline__ __attribute__((always_inline))
#define DRFLAC_GNUC_INLINE_HINT __inline__
#else
#define DRFLAC_INLINE inline __attribute__((always_inline))
#define DRFLAC_GNUC_INLINE_HINT inline
#endif
#if (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 2)) || defined(__clang__)
#define DRFLAC_INLINE DRFLAC_GNUC_INLINE_HINT __attribute__((always_inline))
#else
#define DRFLAC_INLINE DRFLAC_GNUC_INLINE_HINT
#endif
#elif defined(__WATCOMC__)
#define DRFLAC_INLINE __inline
@@ -1378,7 +1384,7 @@ DRFLAC_API drflac_bool32 drflac_next_cuesheet_track(drflac_cuesheet_track_iterat
#define DRFLAC_X64
#elif defined(__i386) || defined(_M_IX86)
#define DRFLAC_X86
#elif defined(__arm__) || defined(_M_ARM) || defined(_M_ARM64)
#elif defined(__arm__) || defined(_M_ARM) || defined(__arm64) || defined(__arm64__) || defined(__aarch64__) || defined(_M_ARM64)
#define DRFLAC_ARM
#endif
@@ -1431,16 +1437,6 @@ Unfortuantely dr_flac depends on this for a few things so we're just going to di
#if defined(DRFLAC_ARM)
#if !defined(DRFLAC_NO_NEON) && (defined(__ARM_NEON) || defined(__aarch64__) || defined(_M_ARM64))
#define DRFLAC_SUPPORT_NEON
#endif
/* Fall back to looking for the #include file. */
#if !defined(__GNUC__) && !defined(__clang__) && defined(__has_include)
#if !defined(DRFLAC_SUPPORT_NEON) && !defined(DRFLAC_NO_NEON) && __has_include(<arm_neon.h>)
#define DRFLAC_SUPPORT_NEON
#endif
#endif
#if defined(DRFLAC_SUPPORT_NEON)
#include <arm_neon.h>
#endif
#endif
@@ -1909,6 +1905,12 @@ static DRFLAC_INLINE drflac_uint32 drflac__be2host_32(drflac_uint32 n)
return n;
}
static DRFLAC_INLINE drflac_uint32 drflac__be2host_32_ptr_unaligned(const void* pData)
{
const drflac_uint8* pNum = (drflac_uint8*)pData;
return *(pNum) << 24 | *(pNum+1) << 16 | *(pNum+2) << 8 | *(pNum+3);
}
static DRFLAC_INLINE drflac_uint64 drflac__be2host_64(drflac_uint64 n)
{
if (drflac__is_little_endian()) {
@@ -1928,6 +1930,12 @@ static DRFLAC_INLINE drflac_uint32 drflac__le2host_32(drflac_uint32 n)
return n;
}
static DRFLAC_INLINE drflac_uint32 drflac__le2host_32_ptr_unaligned(const void* pData)
{
const drflac_uint8* pNum = (drflac_uint8*)pData;
return *pNum | *(pNum+1) << 8 | *(pNum+2) << 16 | *(pNum+3) << 24;
}
static DRFLAC_INLINE drflac_uint32 drflac__unsynchsafe_32(drflac_uint32 n)
{
@@ -2429,6 +2437,10 @@ static DRFLAC_INLINE drflac_bool32 drflac__read_uint32(drflac_bs* bs, unsigned i
if (!drflac__reload_cache(bs)) {
return DRFLAC_FALSE;
}
if (bitCountLo > DRFLAC_CACHE_L1_BITS_REMAINING(bs)) {
/* This happens when we get to end of stream */
return DRFLAC_FALSE;
}
*pResultOut = (resultHi << bitCountLo) | (drflac_uint32)DRFLAC_CACHE_L1_SELECT_AND_SHIFT(bs, bitCountLo);
bs->consumedBits += bitCountLo;
@@ -2872,9 +2884,24 @@ static DRFLAC_INLINE drflac_bool32 drflac__seek_past_next_set_bit(drflac_bs* bs,
}
}
if (bs->cache == 1) {
/* Not catching this would lead to undefined behaviour: a shift of a 32-bit number by 32 or more is undefined */
*pOffsetOut = zeroCounter + (drflac_uint32)DRFLAC_CACHE_L1_BITS_REMAINING(bs) - 1;
if (!drflac__reload_cache(bs)) {
return DRFLAC_FALSE;
}
return DRFLAC_TRUE;
}
setBitOffsetPlus1 = drflac__clz(bs->cache);
setBitOffsetPlus1 += 1;
if (setBitOffsetPlus1 > DRFLAC_CACHE_L1_BITS_REMAINING(bs)) {
/* This happens when we get to end of stream */
return DRFLAC_FALSE;
}
bs->consumedBits += setBitOffsetPlus1;
bs->cache <<= setBitOffsetPlus1;
@@ -2989,6 +3016,25 @@ static drflac_result drflac__read_utf8_coded_number(drflac_bs* bs, drflac_uint64
}
static DRFLAC_INLINE drflac_uint32 drflac__ilog2_u32(drflac_uint32 x)
{
#if 1 /* Needs optimizing. */
drflac_uint32 result = 0;
while (x > 0) {
result += 1;
x >>= 1;
}
return result;
#endif
}
static DRFLAC_INLINE drflac_bool32 drflac__use_64_bit_prediction(drflac_uint32 bitsPerSample, drflac_uint32 order, drflac_uint32 precision)
{
/* https://web.archive.org/web/20220205005724/https://github.com/ietf-wg-cellar/flac-specification/blob/37a49aa48ba4ba12e8757badfc59c0df35435fec/rfc_backmatter.md */
return bitsPerSample + precision + drflac__ilog2_u32(order) > 32;
}
/*
The next two functions are responsible for calculating the prediction.
@@ -2996,6 +3042,9 @@ The next two functions are responsible for calculating the prediction.
When the bits per sample is >16 we need to use 64-bit integer arithmetic because otherwise we'll run out of precision. It's
safe to assume this will be slower on 32-bit platforms so we use a more optimal solution when the bits per sample is <=16.
*/
#if defined(__clang__)
__attribute__((no_sanitize("signed-integer-overflow")))
#endif
static DRFLAC_INLINE drflac_int32 drflac__calculate_prediction_32(drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pDecodedSamples)
{
drflac_int32 prediction = 0;
@@ -3231,7 +3280,7 @@ static DRFLAC_INLINE drflac_int32 drflac__calculate_prediction_64(drflac_uint32
Reference implementation for reading and decoding samples with residual. This is intentionally left unoptimized for the
sake of readability and should only be used as a reference.
*/
static drflac_bool32 drflac__decode_samples_with_residual__rice__reference(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pSamplesOut)
static drflac_bool32 drflac__decode_samples_with_residual__rice__reference(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 lpcOrder, drflac_int32 lpcShift, drflac_uint32 lpcPrecision, const drflac_int32* coefficients, drflac_int32* pSamplesOut)
{
drflac_uint32 i;
@@ -3270,10 +3319,10 @@ static drflac_bool32 drflac__decode_samples_with_residual__rice__reference(drfla
}
if (bitsPerSample+shift >= 32) {
pSamplesOut[i] = decodedRice + drflac__calculate_prediction_64(order, shift, coefficients, pSamplesOut + i);
if (drflac__use_64_bit_prediction(bitsPerSample, lpcOrder, lpcPrecision)) {
pSamplesOut[i] = decodedRice + drflac__calculate_prediction_64(lpcOrder, lpcShift, coefficients, pSamplesOut + i);
} else {
pSamplesOut[i] = decodedRice + drflac__calculate_prediction_32(order, shift, coefficients, pSamplesOut + i);
pSamplesOut[i] = decodedRice + drflac__calculate_prediction_32(lpcOrder, lpcShift, coefficients, pSamplesOut + i);
}
}
@@ -3370,6 +3419,10 @@ static DRFLAC_INLINE drflac_bool32 drflac__read_rice_parts(drflac_bs* bs, drflac
if (!drflac__reload_cache(bs)) {
return DRFLAC_FALSE;
}
if (bitCountLo > DRFLAC_CACHE_L1_BITS_REMAINING(bs)) {
/* This happens when we get to end of stream */
return DRFLAC_FALSE;
}
}
riceParamPart = (drflac_uint32)(resultHi | DRFLAC_CACHE_L1_SELECT_AND_SHIFT_SAFE(bs, bitCountLo));
@@ -3450,6 +3503,10 @@ static DRFLAC_INLINE drflac_bool32 drflac__read_rice_parts_x1(drflac_bs* bs, drf
if (!drflac__reload_cache(bs)) {
return DRFLAC_FALSE;
}
if (riceParamPartLoBitCount > DRFLAC_CACHE_L1_BITS_REMAINING(bs)) {
/* This happens when we get to end of stream */
return DRFLAC_FALSE;
}
bs_cache = bs->cache;
bs_consumedBits = bs->consumedBits + riceParamPartLoBitCount;
@@ -3560,6 +3617,11 @@ static DRFLAC_INLINE drflac_bool32 drflac__seek_rice_parts(drflac_bs* bs, drflac
return DRFLAC_FALSE;
}
if (riceParamPartLoBitCount > DRFLAC_CACHE_L1_BITS_REMAINING(bs)) {
/* This happens when we get to end of stream */
return DRFLAC_FALSE;
}
bs_cache = bs->cache;
bs_consumedBits = bs->consumedBits + riceParamPartLoBitCount;
}
@@ -3646,7 +3708,7 @@ static drflac_bool32 drflac__decode_samples_with_residual__rice__scalar_zeroorde
return DRFLAC_TRUE;
}
static drflac_bool32 drflac__decode_samples_with_residual__rice__scalar(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pSamplesOut)
static drflac_bool32 drflac__decode_samples_with_residual__rice__scalar(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 lpcOrder, drflac_int32 lpcShift, drflac_uint32 lpcPrecision, const drflac_int32* coefficients, drflac_int32* pSamplesOut)
{
drflac_uint32 t[2] = {0x00000000, 0xFFFFFFFF};
drflac_uint32 zeroCountPart0 = 0;
@@ -3664,14 +3726,14 @@ static drflac_bool32 drflac__decode_samples_with_residual__rice__scalar(drflac_b
DRFLAC_ASSERT(bs != NULL);
DRFLAC_ASSERT(pSamplesOut != NULL);
if (order == 0) {
return drflac__decode_samples_with_residual__rice__scalar_zeroorder(bs, bitsPerSample, count, riceParam, order, shift, coefficients, pSamplesOut);
if (lpcOrder == 0) {
return drflac__decode_samples_with_residual__rice__scalar_zeroorder(bs, bitsPerSample, count, riceParam, lpcOrder, lpcShift, coefficients, pSamplesOut);
}
riceParamMask = (drflac_uint32)~((~0UL) << riceParam);
pSamplesOutEnd = pSamplesOut + (count & ~3);
if (bitsPerSample+shift > 32) {
if (drflac__use_64_bit_prediction(bitsPerSample, lpcOrder, lpcPrecision)) {
while (pSamplesOut < pSamplesOutEnd) {
/*
Rice extraction. It's faster to do this one at a time against local variables than it is to use the x4 version
@@ -3699,10 +3761,10 @@ static drflac_bool32 drflac__decode_samples_with_residual__rice__scalar(drflac_b
riceParamPart2 = (riceParamPart2 >> 1) ^ t[riceParamPart2 & 0x01];
riceParamPart3 = (riceParamPart3 >> 1) ^ t[riceParamPart3 & 0x01];
pSamplesOut[0] = riceParamPart0 + drflac__calculate_prediction_64(order, shift, coefficients, pSamplesOut + 0);
pSamplesOut[1] = riceParamPart1 + drflac__calculate_prediction_64(order, shift, coefficients, pSamplesOut + 1);
pSamplesOut[2] = riceParamPart2 + drflac__calculate_prediction_64(order, shift, coefficients, pSamplesOut + 2);
pSamplesOut[3] = riceParamPart3 + drflac__calculate_prediction_64(order, shift, coefficients, pSamplesOut + 3);
pSamplesOut[0] = riceParamPart0 + drflac__calculate_prediction_64(lpcOrder, lpcShift, coefficients, pSamplesOut + 0);
pSamplesOut[1] = riceParamPart1 + drflac__calculate_prediction_64(lpcOrder, lpcShift, coefficients, pSamplesOut + 1);
pSamplesOut[2] = riceParamPart2 + drflac__calculate_prediction_64(lpcOrder, lpcShift, coefficients, pSamplesOut + 2);
pSamplesOut[3] = riceParamPart3 + drflac__calculate_prediction_64(lpcOrder, lpcShift, coefficients, pSamplesOut + 3);
pSamplesOut += 4;
}
@@ -3730,10 +3792,10 @@ static drflac_bool32 drflac__decode_samples_with_residual__rice__scalar(drflac_b
riceParamPart2 = (riceParamPart2 >> 1) ^ t[riceParamPart2 & 0x01];
riceParamPart3 = (riceParamPart3 >> 1) ^ t[riceParamPart3 & 0x01];
pSamplesOut[0] = riceParamPart0 + drflac__calculate_prediction_32(order, shift, coefficients, pSamplesOut + 0);
pSamplesOut[1] = riceParamPart1 + drflac__calculate_prediction_32(order, shift, coefficients, pSamplesOut + 1);
pSamplesOut[2] = riceParamPart2 + drflac__calculate_prediction_32(order, shift, coefficients, pSamplesOut + 2);
pSamplesOut[3] = riceParamPart3 + drflac__calculate_prediction_32(order, shift, coefficients, pSamplesOut + 3);
pSamplesOut[0] = riceParamPart0 + drflac__calculate_prediction_32(lpcOrder, lpcShift, coefficients, pSamplesOut + 0);
pSamplesOut[1] = riceParamPart1 + drflac__calculate_prediction_32(lpcOrder, lpcShift, coefficients, pSamplesOut + 1);
pSamplesOut[2] = riceParamPart2 + drflac__calculate_prediction_32(lpcOrder, lpcShift, coefficients, pSamplesOut + 2);
pSamplesOut[3] = riceParamPart3 + drflac__calculate_prediction_32(lpcOrder, lpcShift, coefficients, pSamplesOut + 3);
pSamplesOut += 4;
}
@@ -3753,10 +3815,10 @@ static drflac_bool32 drflac__decode_samples_with_residual__rice__scalar(drflac_b
/*riceParamPart0 = (riceParamPart0 >> 1) ^ (~(riceParamPart0 & 0x01) + 1);*/
/* Sample reconstruction. */
if (bitsPerSample+shift > 32) {
pSamplesOut[0] = riceParamPart0 + drflac__calculate_prediction_64(order, shift, coefficients, pSamplesOut + 0);
if (drflac__use_64_bit_prediction(bitsPerSample, lpcOrder, lpcPrecision)) {
pSamplesOut[0] = riceParamPart0 + drflac__calculate_prediction_64(lpcOrder, lpcShift, coefficients, pSamplesOut + 0);
} else {
pSamplesOut[0] = riceParamPart0 + drflac__calculate_prediction_32(order, shift, coefficients, pSamplesOut + 0);
pSamplesOut[0] = riceParamPart0 + drflac__calculate_prediction_32(lpcOrder, lpcShift, coefficients, pSamplesOut + 0);
}
i += 1;
@@ -4212,20 +4274,20 @@ static drflac_bool32 drflac__decode_samples_with_residual__rice__sse41_64(drflac
return DRFLAC_TRUE;
}
static drflac_bool32 drflac__decode_samples_with_residual__rice__sse41(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pSamplesOut)
static drflac_bool32 drflac__decode_samples_with_residual__rice__sse41(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 lpcOrder, drflac_int32 lpcShift, drflac_uint32 lpcPrecision, const drflac_int32* coefficients, drflac_int32* pSamplesOut)
{
DRFLAC_ASSERT(bs != NULL);
DRFLAC_ASSERT(pSamplesOut != NULL);
/* In my testing the order is rarely > 12, so in this case I'm going to simplify the SSE implementation by only handling order <= 12. */
if (order > 0 && order <= 12) {
if (bitsPerSample+shift > 32) {
return drflac__decode_samples_with_residual__rice__sse41_64(bs, count, riceParam, order, shift, coefficients, pSamplesOut);
if (lpcOrder > 0 && lpcOrder <= 12) {
if (drflac__use_64_bit_prediction(bitsPerSample, lpcOrder, lpcPrecision)) {
return drflac__decode_samples_with_residual__rice__sse41_64(bs, count, riceParam, lpcOrder, lpcShift, coefficients, pSamplesOut);
} else {
return drflac__decode_samples_with_residual__rice__sse41_32(bs, count, riceParam, order, shift, coefficients, pSamplesOut);
return drflac__decode_samples_with_residual__rice__sse41_32(bs, count, riceParam, lpcOrder, lpcShift, coefficients, pSamplesOut);
}
} else {
return drflac__decode_samples_with_residual__rice__scalar(bs, bitsPerSample, count, riceParam, order, shift, coefficients, pSamplesOut);
return drflac__decode_samples_with_residual__rice__scalar(bs, bitsPerSample, count, riceParam, lpcOrder, lpcShift, lpcPrecision, coefficients, pSamplesOut);
}
}
#endif
@@ -4562,7 +4624,7 @@ static drflac_bool32 drflac__decode_samples_with_residual__rice__neon_64(drflac_
/*
Pre-loading the coefficients and prior samples is annoying because we need to ensure we don't try reading more than
what's available in the input buffers. It would be conenient to use a fall-through switch to do this, but this results
what's available in the input buffers. It would be convenient to use a fall-through switch to do this, but this results
in strict aliasing warnings with GCC. To work around this I'm just doing something hacky. This feels a bit convoluted
so I think there's opportunity for this to be simplified.
*/
@@ -4710,41 +4772,41 @@ static drflac_bool32 drflac__decode_samples_with_residual__rice__neon_64(drflac_
return DRFLAC_TRUE;
}
static drflac_bool32 drflac__decode_samples_with_residual__rice__neon(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pSamplesOut)
static drflac_bool32 drflac__decode_samples_with_residual__rice__neon(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 lpcOrder, drflac_int32 lpcShift, drflac_uint32 lpcPrecision, const drflac_int32* coefficients, drflac_int32* pSamplesOut)
{
DRFLAC_ASSERT(bs != NULL);
DRFLAC_ASSERT(pSamplesOut != NULL);
/* In my testing the order is rarely > 12, so in this case I'm going to simplify the NEON implementation by only handling order <= 12. */
if (order > 0 && order <= 12) {
if (bitsPerSample+shift > 32) {
return drflac__decode_samples_with_residual__rice__neon_64(bs, count, riceParam, order, shift, coefficients, pSamplesOut);
if (lpcOrder > 0 && lpcOrder <= 12) {
if (drflac__use_64_bit_prediction(bitsPerSample, lpcOrder, lpcPrecision)) {
return drflac__decode_samples_with_residual__rice__neon_64(bs, count, riceParam, lpcOrder, lpcShift, coefficients, pSamplesOut);
} else {
return drflac__decode_samples_with_residual__rice__neon_32(bs, count, riceParam, order, shift, coefficients, pSamplesOut);
return drflac__decode_samples_with_residual__rice__neon_32(bs, count, riceParam, lpcOrder, lpcShift, coefficients, pSamplesOut);
}
} else {
return drflac__decode_samples_with_residual__rice__scalar(bs, bitsPerSample, count, riceParam, order, shift, coefficients, pSamplesOut);
return drflac__decode_samples_with_residual__rice__scalar(bs, bitsPerSample, count, riceParam, lpcOrder, lpcShift, lpcPrecision, coefficients, pSamplesOut);
}
}
#endif
static drflac_bool32 drflac__decode_samples_with_residual__rice(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pSamplesOut)
static drflac_bool32 drflac__decode_samples_with_residual__rice(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 lpcOrder, drflac_int32 lpcShift, drflac_uint32 lpcPrecision, const drflac_int32* coefficients, drflac_int32* pSamplesOut)
{
#if defined(DRFLAC_SUPPORT_SSE41)
if (drflac__gIsSSE41Supported) {
return drflac__decode_samples_with_residual__rice__sse41(bs, bitsPerSample, count, riceParam, order, shift, coefficients, pSamplesOut);
return drflac__decode_samples_with_residual__rice__sse41(bs, bitsPerSample, count, riceParam, lpcOrder, lpcShift, lpcPrecision, coefficients, pSamplesOut);
} else
#elif defined(DRFLAC_SUPPORT_NEON)
if (drflac__gIsNEONSupported) {
return drflac__decode_samples_with_residual__rice__neon(bs, bitsPerSample, count, riceParam, order, shift, coefficients, pSamplesOut);
return drflac__decode_samples_with_residual__rice__neon(bs, bitsPerSample, count, riceParam, lpcOrder, lpcShift, lpcPrecision, coefficients, pSamplesOut);
} else
#endif
{
/* Scalar fallback. */
#if 0
return drflac__decode_samples_with_residual__rice__reference(bs, bitsPerSample, count, riceParam, order, shift, coefficients, pSamplesOut);
return drflac__decode_samples_with_residual__rice__reference(bs, bitsPerSample, count, riceParam, lpcOrder, lpcShift, lpcPrecision, coefficients, pSamplesOut);
#else
return drflac__decode_samples_with_residual__rice__scalar(bs, bitsPerSample, count, riceParam, order, shift, coefficients, pSamplesOut);
return drflac__decode_samples_with_residual__rice__scalar(bs, bitsPerSample, count, riceParam, lpcOrder, lpcShift, lpcPrecision, coefficients, pSamplesOut);
#endif
}
}
@@ -4765,7 +4827,10 @@ static drflac_bool32 drflac__read_and_seek_residual__rice(drflac_bs* bs, drflac_
return DRFLAC_TRUE;
}
static drflac_bool32 drflac__decode_samples_with_residual__unencoded(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 unencodedBitsPerSample, drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pSamplesOut)
#if defined(__clang__)
__attribute__((no_sanitize("signed-integer-overflow")))
#endif
static drflac_bool32 drflac__decode_samples_with_residual__unencoded(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 unencodedBitsPerSample, drflac_uint32 lpcOrder, drflac_int32 lpcShift, drflac_uint32 lpcPrecision, const drflac_int32* coefficients, drflac_int32* pSamplesOut)
{
drflac_uint32 i;
@@ -4782,10 +4847,10 @@ static drflac_bool32 drflac__decode_samples_with_residual__unencoded(drflac_bs*
pSamplesOut[i] = 0;
}
if (bitsPerSample >= 24) {
pSamplesOut[i] += drflac__calculate_prediction_64(order, shift, coefficients, pSamplesOut + i);
if (drflac__use_64_bit_prediction(bitsPerSample, lpcOrder, lpcPrecision)) {
pSamplesOut[i] += drflac__calculate_prediction_64(lpcOrder, lpcShift, coefficients, pSamplesOut + i);
} else {
pSamplesOut[i] += drflac__calculate_prediction_32(order, shift, coefficients, pSamplesOut + i);
pSamplesOut[i] += drflac__calculate_prediction_32(lpcOrder, lpcShift, coefficients, pSamplesOut + i);
}
}
@@ -4798,7 +4863,7 @@ Reads and decodes the residual for the sub-frame the decoder is currently sittin
when the decoder is sitting at the very start of the RESIDUAL block. The first <order> residuals will be ignored. The
<blockSize> and <order> parameters are used to determine how many residual values need to be decoded.
*/
static drflac_bool32 drflac__decode_samples_with_residual(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 blockSize, drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pDecodedSamples)
static drflac_bool32 drflac__decode_samples_with_residual(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 blockSize, drflac_uint32 lpcOrder, drflac_int32 lpcShift, drflac_uint32 lpcPrecision, const drflac_int32* coefficients, drflac_int32* pDecodedSamples)
{
drflac_uint8 residualMethod;
drflac_uint8 partitionOrder;
@@ -4818,7 +4883,7 @@ static drflac_bool32 drflac__decode_samples_with_residual(drflac_bs* bs, drflac_
}
/* Ignore the first <order> values. */
pDecodedSamples += order;
pDecodedSamples += lpcOrder;
if (!drflac__read_uint8(bs, 4, &partitionOrder)) {
return DRFLAC_FALSE;
@@ -4833,11 +4898,11 @@ static drflac_bool32 drflac__decode_samples_with_residual(drflac_bs* bs, drflac_
}
/* Validation check. */
if ((blockSize / (1 << partitionOrder)) < order) {
if ((blockSize / (1 << partitionOrder)) < lpcOrder) {
return DRFLAC_FALSE;
}
samplesInPartition = (blockSize / (1 << partitionOrder)) - order;
samplesInPartition = (blockSize / (1 << partitionOrder)) - lpcOrder;
partitionsRemaining = (1 << partitionOrder);
for (;;) {
drflac_uint8 riceParam = 0;
@@ -4858,7 +4923,7 @@ static drflac_bool32 drflac__decode_samples_with_residual(drflac_bs* bs, drflac_
}
if (riceParam != 0xFF) {
if (!drflac__decode_samples_with_residual__rice(bs, bitsPerSample, samplesInPartition, riceParam, order, shift, coefficients, pDecodedSamples)) {
if (!drflac__decode_samples_with_residual__rice(bs, bitsPerSample, samplesInPartition, riceParam, lpcOrder, lpcShift, lpcPrecision, coefficients, pDecodedSamples)) {
return DRFLAC_FALSE;
}
} else {
@@ -4867,7 +4932,7 @@ static drflac_bool32 drflac__decode_samples_with_residual(drflac_bs* bs, drflac_
return DRFLAC_FALSE;
}
if (!drflac__decode_samples_with_residual__unencoded(bs, bitsPerSample, samplesInPartition, unencodedBitsPerSample, order, shift, coefficients, pDecodedSamples)) {
if (!drflac__decode_samples_with_residual__unencoded(bs, bitsPerSample, samplesInPartition, unencodedBitsPerSample, lpcOrder, lpcShift, lpcPrecision, coefficients, pDecodedSamples)) {
return DRFLAC_FALSE;
}
}
@@ -5036,7 +5101,7 @@ static drflac_bool32 drflac__decode_samples__fixed(drflac_bs* bs, drflac_uint32
pDecodedSamples[i] = sample;
}
if (!drflac__decode_samples_with_residual(bs, subframeBitsPerSample, blockSize, lpcOrder, 0, lpcCoefficientsTable[lpcOrder], pDecodedSamples)) {
if (!drflac__decode_samples_with_residual(bs, subframeBitsPerSample, blockSize, lpcOrder, 0, 4, lpcCoefficientsTable[lpcOrder], pDecodedSamples)) {
return DRFLAC_FALSE;
}
@@ -5091,7 +5156,7 @@ static drflac_bool32 drflac__decode_samples__lpc(drflac_bs* bs, drflac_uint32 bl
}
}
if (!drflac__decode_samples_with_residual(bs, bitsPerSample, blockSize, lpcOrder, lpcShift, coefficients, pDecodedSamples)) {
if (!drflac__decode_samples_with_residual(bs, bitsPerSample, blockSize, lpcOrder, lpcShift, lpcPrecision, coefficients, pDecodedSamples)) {
return DRFLAC_FALSE;
}
@@ -5219,6 +5284,9 @@ static drflac_bool32 drflac__read_next_flac_frame_header(drflac_bs* bs, drflac_u
return DRFLAC_FALSE;
}
crc8 = drflac_crc8(crc8, header->blockSizeInPCMFrames, 16);
if (header->blockSizeInPCMFrames == 0xFFFF) {
return DRFLAC_FALSE; /* Frame is too big. This is the size of the frame minus 1. The STREAMINFO block defines the max block size which is 16-bits. Adding one will make it 17 bits and therefore too big. */
}
header->blockSizeInPCMFrames += 1;
} else {
DRFLAC_ASSERT(blockSize >= 8);
@@ -5257,6 +5325,11 @@ static drflac_bool32 drflac__read_next_flac_frame_header(drflac_bs* bs, drflac_u
header->bitsPerSample = streaminfoBitsPerSample;
}
if (header->bitsPerSample != streaminfoBitsPerSample) {
/* If this subframe has a different bitsPerSample then streaminfo or the first frame, reject it */
return DRFLAC_FALSE;
}
if (!drflac__read_uint8(bs, 8, &header->crc8)) {
return DRFLAC_FALSE;
}
@@ -5343,6 +5416,11 @@ static drflac_bool32 drflac__decode_subframe(drflac_bs* bs, drflac_frame* frame,
subframeBitsPerSample += 1;
}
if (subframeBitsPerSample > 32) {
/* libFLAC and ffmpeg reject 33-bit subframes as well */
return DRFLAC_FALSE;
}
/* Need to handle wasted bits per sample. */
if (pSubframe->wastedBitsPerSample >= subframeBitsPerSample) {
return DRFLAC_FALSE;
@@ -6485,7 +6563,7 @@ static drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, d
pRunningData = (const char*)pRawData;
pRunningDataEnd = (const char*)pRawData + blockSize;
metadata.data.vorbis_comment.vendorLength = drflac__le2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4;
metadata.data.vorbis_comment.vendorLength = drflac__le2host_32_ptr_unaligned(pRunningData); pRunningData += 4;
/* Need space for the rest of the block */
if ((pRunningDataEnd - pRunningData) - 4 < (drflac_int64)metadata.data.vorbis_comment.vendorLength) { /* <-- Note the order of operations to avoid overflow to a valid value */
@@ -6493,7 +6571,7 @@ static drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, d
return DRFLAC_FALSE;
}
metadata.data.vorbis_comment.vendor = pRunningData; pRunningData += metadata.data.vorbis_comment.vendorLength;
metadata.data.vorbis_comment.commentCount = drflac__le2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4;
metadata.data.vorbis_comment.commentCount = drflac__le2host_32_ptr_unaligned(pRunningData); pRunningData += 4;
/* Need space for 'commentCount' comments after the block, which at minimum is a drflac_uint32 per comment */
if ((pRunningDataEnd - pRunningData) / sizeof(drflac_uint32) < metadata.data.vorbis_comment.commentCount) { /* <-- Note the order of operations to avoid overflow to a valid value */
@@ -6511,7 +6589,7 @@ static drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, d
return DRFLAC_FALSE;
}
commentLength = drflac__le2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4;
commentLength = drflac__le2host_32_ptr_unaligned(pRunningData); pRunningData += 4;
if (pRunningDataEnd - pRunningData < (drflac_int64)commentLength) { /* <-- Note the order of operations to avoid overflow to a valid value */
drflac__free_from_callbacks(pRawData, pAllocationCallbacks);
return DRFLAC_FALSE;
@@ -6620,8 +6698,8 @@ static drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, d
pRunningData = (const char*)pRawData;
pRunningDataEnd = (const char*)pRawData + blockSize;
metadata.data.picture.type = drflac__be2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4;
metadata.data.picture.mimeLength = drflac__be2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4;
metadata.data.picture.type = drflac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4;
metadata.data.picture.mimeLength = drflac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4;
/* Need space for the rest of the block */
if ((pRunningDataEnd - pRunningData) - 24 < (drflac_int64)metadata.data.picture.mimeLength) { /* <-- Note the order of operations to avoid overflow to a valid value */
@@ -6629,7 +6707,7 @@ static drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, d
return DRFLAC_FALSE;
}
metadata.data.picture.mime = pRunningData; pRunningData += metadata.data.picture.mimeLength;
metadata.data.picture.descriptionLength = drflac__be2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4;
metadata.data.picture.descriptionLength = drflac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4;
/* Need space for the rest of the block */
if ((pRunningDataEnd - pRunningData) - 20 < (drflac_int64)metadata.data.picture.descriptionLength) { /* <-- Note the order of operations to avoid overflow to a valid value */
@@ -6637,11 +6715,11 @@ static drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, d
return DRFLAC_FALSE;
}
metadata.data.picture.description = pRunningData; pRunningData += metadata.data.picture.descriptionLength;
metadata.data.picture.width = drflac__be2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4;
metadata.data.picture.height = drflac__be2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4;
metadata.data.picture.colorDepth = drflac__be2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4;
metadata.data.picture.indexColorCount = drflac__be2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4;
metadata.data.picture.pictureDataSize = drflac__be2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4;
metadata.data.picture.width = drflac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4;
metadata.data.picture.height = drflac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4;
metadata.data.picture.colorDepth = drflac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4;
metadata.data.picture.indexColorCount = drflac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4;
metadata.data.picture.pictureDataSize = drflac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4;
metadata.data.picture.pPictureData = (const drflac_uint8*)pRunningData;
/* Need space for the picture after the block */
@@ -7865,7 +7943,7 @@ static drflac* drflac_open_with_metadata_private(drflac_read_proc onRead, drflac
#ifndef DR_FLAC_NO_OGG
if (init.container == drflac_container_ogg) {
drflac_oggbs* pInternalOggbs = (drflac_oggbs*)((drflac_uint8*)pFlac->pDecodedSamples + decodedSamplesAllocationSize + seektableSize);
*pInternalOggbs = oggbs;
DRFLAC_COPY_MEMORY(pInternalOggbs, &oggbs, sizeof(oggbs));
/* The Ogg bistream needs to be layered on top of the original bitstream. */
pFlac->bs.onRead = drflac__on_read_ogg;
@@ -11786,7 +11864,7 @@ DRFLAC_API const char* drflac_next_vorbis_comment(drflac_vorbis_comment_iterator
return NULL;
}
length = drflac__le2host_32(*(const drflac_uint32*)pIter->pRunningData);
length = drflac__le2host_32_ptr_unaligned(pIter->pRunningData);
pIter->pRunningData += 4;
pComment = pIter->pRunningData;
@@ -11856,6 +11934,22 @@ DRFLAC_API drflac_bool32 drflac_next_cuesheet_track(drflac_cuesheet_track_iterat
/*
REVISION HISTORY
================
v0.12.38 - 2022-04-10
- Fix compilation error on older versions of GCC.
v0.12.37 - 2022-02-12
- Improve ARM detection.
v0.12.36 - 2022-02-07
- Fix a compilation error with the ARM build.
v0.12.35 - 2022-02-06
- Fix a bug due to underestimating the amount of precision required for the prediction stage.
- Fix some bugs found from fuzz testing.
v0.12.34 - 2022-01-07
- Fix some misalignment bugs when reading metadata.
v0.12.33 - 2021-12-22
- Fix a bug with seeking when the seek table does not start at PCM frame 0.
+5
View File
@@ -78,6 +78,11 @@ bool Audio::setMixWithSystem(bool mix)
#endif
}
void Audio::setPlaybackDevice(const char */*name*/)
{
throw love::Exception("Re-setting output device is not supported.");
}
StringMap<Audio::DistanceModel, Audio::DISTANCE_MAX_ENUM>::Entry Audio::distanceModelEntries[] =
{
{"none", Audio::DISTANCE_NONE},
+15
View File
@@ -297,6 +297,21 @@ public:
virtual void pauseContext() = 0;
virtual void resumeContext() = 0;
/**
* Get current playback device name.
*/
virtual std::string getPlaybackDevice() = 0;
/**
* Retrieve list of available playback devices.
*/
virtual void getPlaybackDevices(std::vector<std::string> &list) = 0;
/**
* Set the current playback device to specified device name.
*/
virtual void setPlaybackDevice(const char *name);
private:
static StringMap<DistanceModel, DISTANCE_MAX_ENUM>::Entry distanceModelEntries[];
+9
View File
@@ -211,6 +211,15 @@ void Audio::resumeContext()
{
}
std::string Audio::getPlaybackDevice()
{
return "";
}
void Audio::getPlaybackDevices(std::vector<std::string> &/*list*/)
{
}
} // null
} // audio
+3
View File
@@ -89,6 +89,9 @@ public:
void pauseContext();
void resumeContext();
std::string getPlaybackDevice();
void getPlaybackDevices(std::vector<std::string> &list);
private:
float volume;
DistanceModel distanceModel;
+65 -5
View File
@@ -93,6 +93,18 @@ ALenum Audio::getFormat(int bitDepth, int channels)
return AL_NONE;
}
static const char *getDeviceSpecifier(ALCdevice *device)
{
#ifndef ALC_ALL_DEVICES_SPECIFIER
constexpr ALCenum ALC_ALL_DEVICES_SPECIFIER = 0x1013;
#endif
static ALCenum deviceEnum = alcIsExtensionPresent(nullptr, "ALC_ENUMERATE_ALL_EXT") == ALC_TRUE
? ALC_ALL_DEVICES_SPECIFIER
: ALC_DEVICE_SPECIFIER;
return alcGetString(device, deviceEnum);
}
Audio::Audio()
: device(nullptr)
, context(nullptr)
@@ -100,6 +112,9 @@ Audio::Audio()
, poolThread(nullptr)
, distanceModel(DISTANCE_INVERSE_CLAMPED)
{
attribs.push_back(0);
attribs.push_back(0);
// Before opening new device, check if recording
// is requested.
if (getRequestRecordingPermission())
@@ -122,12 +137,11 @@ Audio::Audio()
throw love::Exception("Could not open device.");
#ifdef ALC_EXT_EFX
ALint attribs[4] = { ALC_MAX_AUXILIARY_SENDS, MAX_SOURCE_EFFECTS, 0, 0 };
#else
ALint *attribs = nullptr;
attribs.insert(attribs.begin(), ALC_MAX_AUXILIARY_SENDS);
attribs.insert(attribs.begin() + 1, MAX_SOURCE_EFFECTS);
#endif
context = alcCreateContext(device, attribs);
context = alcCreateContext(device, attribs.data());
if (context == nullptr)
throw love::Exception("Could not create context.");
@@ -165,7 +179,7 @@ Audio::Audio()
try
{
pool = new Pool();
pool = new Pool(device);
}
catch (love::Exception &)
{
@@ -314,6 +328,52 @@ void Audio::resumeContext()
alcMakeContextCurrent(context);
}
std::string Audio::getPlaybackDevice()
{
const char *dev = getDeviceSpecifier(device);
if (dev == nullptr)
throw Exception("Failed to get current device: %s", alcGetString(device, alcGetError(device)));
return dev;
}
void Audio::getPlaybackDevices(std::vector<std::string> &list)
{
const char *devices = getDeviceSpecifier(nullptr);
if (devices == nullptr)
throw Exception("Failed to enumerate devices: %s", alcGetString(nullptr, alcGetError(nullptr)));
for (const char *device = devices; *device; device++)
{
list.emplace_back(device);
device += list.back().length();
}
}
void Audio::setPlaybackDevice(const char* name)
{
#ifndef ALC_SOFT_reopen_device
typedef ALCboolean (ALC_APIENTRY*LPALCREOPENDEVICESOFT)(ALCdevice *device,
const ALCchar *deviceName, const ALCint *attribs);
#endif
static LPALCREOPENDEVICESOFT alcReopenDeviceSOFT = alcIsExtensionPresent(device, "ALC_SOFT_reopen_device") == ALC_TRUE
? (LPALCREOPENDEVICESOFT) alcGetProcAddress(device, "alcReopenDeviceSOFT")
: nullptr;
if (alcReopenDeviceSOFT == nullptr)
{
// Default implementation throws exception. To make
// error message consistent, call the base class.
love::audio::Audio::setPlaybackDevice(name);
return;
}
if (alcReopenDeviceSOFT(device, (const ALCchar *) name, attribs.data()) == ALC_FALSE)
throw love::Exception("Cannot set output device: %s", alcGetString(device, alcGetError(device)));
}
void Audio::setVolume(float volume)
{
alListenerf(AL_GAIN, volume);
+5
View File
@@ -127,6 +127,10 @@ public:
bool getEffectID(const char *name, ALuint &id);
std::string getPlaybackDevice();
void getPlaybackDevices(std::vector<std::string> &list);
void setPlaybackDevice(const char *name);
private:
void initializeEFX();
// The OpenAL device.
@@ -137,6 +141,7 @@ private:
// The OpenAL context.
ALCcontext *context;
std::vector<ALCint> attribs;
// The OpenAL effects
struct EffectMapStorage
+50 -2
View File
@@ -20,6 +20,7 @@
#include "Pool.h"
#include "event/Event.h"
#include "Source.h"
namespace love
@@ -29,8 +30,20 @@ namespace audio
namespace openal
{
Pool::Pool()
: sources()
static Variant::SharedTable *putSourcesAsSharedTable(std::vector<audio::Source *> &sources)
{
Variant::SharedTable *table = new Variant::SharedTable();
for (int i = 0; i < sources.size(); i++)
table->pairs.emplace_back((double) (i + 1), Variant(&Source::type, sources[i]));
return table;
}
Pool::Pool(ALCdevice *device)
: device(device)
, sources()
, disconnectNotified(false)
, totalSources(0)
{
// Clear errors.
@@ -101,8 +114,43 @@ bool Pool::isPlaying(Source *s)
void Pool::update()
{
#ifndef ALC_CONNECTED
constexpr ALCenum ALC_CONNECTED = 0x313;
#endif
thread::Lock lock(mutex);
static bool disconnectExtSupported = alcIsExtensionPresent(device, "ALC_EXT_Disconnect") == ALC_TRUE;
// Device disconnection event
if (disconnectExtSupported)
{
auto eventModule = Module::getInstance<event::Event>(Module::M_EVENT);
if (eventModule)
{
ALCint connected;
alcGetIntegerv(device, ALC_CONNECTED, 1, &connected);
if (connected)
disconnectNotified = false;
else if (!disconnectNotified)
{
// Get all sources in this Pool then stop it
// since they're all internally stopped.
std::vector<audio::Source *> sources = getPlayingSources();
Source::stop(sources);
std::vector<Variant> vargs;
vargs.emplace_back(putSourcesAsSharedTable(sources));
StrongRef<event::Message> msg(new event::Message("audiodisconnected", vargs), Acquire::NORETAIN);
eventModule->push(msg);
disconnectNotified = true;
}
}
}
std::vector<Source *> torelease;
for (const auto &i : playing)
+7 -1
View File
@@ -64,7 +64,7 @@ class Pool
{
public:
Pool();
Pool(ALCdevice *device);
~Pool();
/**
@@ -101,9 +101,15 @@ private:
// Maximum possible number of OpenAL sources the pool attempts to generate.
static const int MAX_SOURCES = 64;
// Current OpenAL device
ALCdevice *device;
// OpenAL sources
ALuint sources[MAX_SOURCES];
// Is device disconnection has been notified?
bool disconnectNotified;
// Total number of created sources in the pool.
int totalSources;
+73 -21
View File
@@ -48,32 +48,35 @@ int w_newSource(lua_State *L)
{
Source::Type stype = Source::TYPE_STREAM;
if (!luax_istype(L, 1, love::sound::SoundData::type) && !luax_istype(L, 1, love::sound::Decoder::type))
if (!luax_istype(L, 1, love::sound::SoundData::type))
{
const char *stypestr = luaL_checkstring(L, 2);
if (stypestr && !Source::getConstant(stypestr, stype))
return luax_enumerror(L, "source type", Source::getConstants(stype), stypestr);
if (!luax_istype(L, 1, love::sound::Decoder::type))
{
const char *stypestr = luaL_checkstring(L, 2);
if (stypestr && !Source::getConstant(stypestr, stype))
return luax_enumerror(L, "source type", Source::getConstants(stype), stypestr);
if (stype == Source::TYPE_QUEUE)
return luaL_error(L, "Cannot create queueable sources using newSource. Use newQueueableSource instead.");
}
if (stype == Source::TYPE_QUEUE)
return luaL_error(L, "Cannot create queueable sources using newSource. Use newQueueableSource instead.");
}
if (love::filesystem::luax_cangetdata(L, 1))
{
// stream type
if (stype == Source::TYPE_STATIC)
lua_pushstring(L, "memory");
else if (!lua_isnone(L, 3))
lua_pushvalue(L, 3);
else
if (love::filesystem::luax_cangetdata(L, 1))
{
// stream type
if (stype == Source::TYPE_STATIC)
lua_pushstring(L, "memory");
else if (!lua_isnone(L, 3))
lua_pushvalue(L, 3);
else
lua_pushnil(L);
// buffer size
lua_pushnil(L);
// buffer size
lua_pushnil(L);
// (file, buffer size, stream type)
int idxs[] = { 1, lua_gettop(L), lua_gettop(L) - 1 };
luax_convobj(L, idxs, 3, "sound", "newDecoder");
// (file, buffer size, stream type)
int idxs[] = { 1, lua_gettop(L), lua_gettop(L) - 1 };
luax_convobj(L, idxs, 3, "sound", "newDecoder");
}
}
if (stype == Source::TYPE_STATIC && luax_istype(L, 1, love::sound::Decoder::type))
@@ -543,6 +546,52 @@ int w_setMixWithSystem(lua_State *L)
return 1;
}
int w_getPlaybackDevice(lua_State* L)
{
std::string device;
luax_catchexcept(L, [&]() { device = instance()->getPlaybackDevice(); });
luax_pushstring(L, device);
return 1;
}
int w_getPlaybackDevices(lua_State* L)
{
std::vector<std::string> list;
luax_catchexcept(L, [&]() { instance()->getPlaybackDevices(list); });
lua_createtable(L, 0, (int) list.size());
for (int i = 0; i < (int) list.size(); i++)
{
lua_pushnumber(L, i + 1);
lua_pushstring(L, list[i].c_str());
lua_rawset(L, -3);
}
return 1;
}
int w_setPlaybackDevice(lua_State* L)
{
const char *device = luaL_optstring(L, 1, nullptr);
try
{
instance()->setPlaybackDevice(device);
luax_pushboolean(L, true);
return 1;
}
catch (love::Exception& e)
{
luax_pushboolean(L, false);
lua_pushstring(L, e.what());
return 2;
}
// To avoid compiler warning
return 0;
}
// List of functions to wrap.
static const luaL_Reg functions[] =
{
@@ -574,6 +623,9 @@ static const luaL_Reg functions[] =
{ "getMaxSourceEffects", w_getMaxSourceEffects },
{ "isEffectsSupported", w_isEffectsSupported },
{ "setMixWithSystem", w_setMixWithSystem },
{ "getPlaybackDevice", w_getPlaybackDevice },
{ "getPlaybackDevices", w_getPlaybackDevices },
{ "setPlaybackDevice", w_setPlaybackDevice },
{ 0, 0 }
};
+20 -1
View File
@@ -22,6 +22,10 @@
#include "NativeFile.h"
#include "common/utf8.h"
#ifdef LOVE_ANDROID
#include "common/android.h"
#endif
// Assume POSIX or Visual Studio.
#include <sys/types.h>
#include <sys/stat.h>
@@ -84,7 +88,22 @@ bool NativeFile::open(Mode newmode)
if (file != nullptr)
return false;
#ifdef LOVE_WINDOWS
#if defined(LOVE_ANDROID)
// Try to handle content:// URI
int fd = love::android::getFDFromContentProtocol(filename.c_str());
if (fd != -1)
{
if (newmode != MODE_READ)
{
::close(fd);
throw love::Exception("%s is read-only.", filename.c_str());
}
file = fdopen(fd, "rb");
}
else
file = fopen(filename.c_str(), getModeString(newmode));
#elif defined(LOVE_WINDOWS)
// make sure non-ASCII filenames work.
std::wstring modestr = to_widestr(getModeString(newmode));
std::wstring wfilename = to_widestr(filename);
+10 -26
View File
@@ -236,8 +236,6 @@ bool Filesystem::setSource(const char *source)
if (!love::android::createStorageDirectories())
SDL_Log("Error creating storage directories!");
new_search_path = "";
PHYSFS_Io *gameLoveIO;
bool hasFusedGame = love::android::checkFusedGame((void **) &gameLoveIO);
bool isAAssetMounted = false;
@@ -263,38 +261,24 @@ bool Filesystem::setSource(const char *source)
if (!isAAssetMounted)
{
new_search_path = love::android::getSelectedGameFile();
// try mounting first, if that fails, load to memory and mount
if (!PHYSFS_mount(new_search_path.c_str(), nullptr, 1))
// Is this love2d://fd/ URIs?
int fd = love::android::getFDFromLoveProtocol(new_search_path.c_str());
if (fd != -1)
{
// PHYSFS cannot yet mount a zip file inside an .apk
SDL_Log("Mounting %s did not work. Loading to memory.",
new_search_path.c_str());
char* game_archive_ptr = NULL;
size_t game_archive_size = 0;
if (!love::android::loadGameArchiveToMemory(
new_search_path.c_str(), &game_archive_ptr,
&game_archive_size))
PHYSFS_Io *io = (PHYSFS_Io *) love::android::getIOFromFD(fd);
if (PHYSFS_mountIo(io, "LOVE.FD", nullptr, 0))
{
SDL_Log("Failure memory loading archive %s", new_search_path.c_str());
return false;
}
if (!PHYSFS_mountMemory(
game_archive_ptr, game_archive_size,
love::android::freeGameArchiveMemory, "archive.zip", "/", 0))
{
SDL_Log("Failure mounting in-memory archive.");
love::android::freeGameArchiveMemory(game_archive_ptr);
return false;
gameSource = new_search_path;
return true;
}
}
}
#else
#endif
// Add the directory.
if (!PHYSFS_mount(new_search_path.c_str(), nullptr, 1))
return false;
#endif
// Save the game source.
gameSource = new_search_path;
+26 -30
View File
@@ -305,7 +305,7 @@ File *luax_getfile(lua_State *L, int idx)
return file;
}
FileData *luax_getfiledata(lua_State *L, int idx)
FileData *luax_getfiledata(lua_State *L, int idx, bool ioerror)
{
FileData *data = nullptr;
File *file = nullptr;
@@ -325,18 +325,33 @@ FileData *luax_getfiledata(lua_State *L, int idx)
luaL_argerror(L, idx, "filename, File, or FileData expected");
return nullptr; // Never reached.
}
if (file)
else if (file && !data)
{
luax_catchexcept(L,
[&]() { data = file->read(); },
[&](bool) { file->release(); }
);
try
{
data = file->read();
}
catch (love::Exception &e)
{
file->release();
if (ioerror)
luax_ioError(L, "%s", e.what());
else
luaL_error(L, "%s", e.what());
return nullptr; // Never reached.
}
file->release();
}
return data;
}
FileData *luax_getfiledata(lua_State *L, int idx)
{
return luax_getfiledata(L, idx, false);
}
Data *luax_getdata(lua_State *L, int idx)
{
Data *data = nullptr;
@@ -389,29 +404,10 @@ int w_newFileData(lua_State *L)
// Single argument: treat as filepath or File.
if (lua_gettop(L) == 1)
{
// We don't use luax_getfiledata because we want to use an ioError.
if (lua_isstring(L, 1))
luax_convobj(L, 1, "filesystem", "newFile");
// Get FileData from the File.
if (luax_istype(L, 1, File::type))
{
File *file = luax_checkfile(L, 1);
StrongRef<FileData> data;
try
{
data.set(file->read(), Acquire::NORETAIN);
}
catch (love::Exception &e)
{
return luax_ioError(L, "%s", e.what());
}
luax_pushtype(L, data);
return 1;
}
else
return luaL_argerror(L, 1, "filename or File expected");
FileData *data = luax_getfiledata(L, 1, true);
luax_pushtype(L, data);
data->release();
return 1;
}
size_t length = 0;
+3 -2
View File
@@ -36,6 +36,7 @@ Buffer::Buffer(Graphics *gfx, const Settings &settings, const std::vector<DataDe
, usageFlags(settings.usageFlags)
, dataUsage(settings.dataUsage)
, mapped(false)
, mappedType(MAP_WRITE_INVALIDATE)
, immutable(false)
{
if (size == 0 && arraylength == 0)
@@ -61,8 +62,8 @@ Buffer::Buffer(Graphics *gfx, const Settings &settings, const std::vector<DataDe
if (storagebuffer && dataUsage == BUFFERDATAUSAGE_STREAM)
throw love::Exception("Buffers created with 'stream' data usage cannot be used as a shader storage buffer.");
if (dataUsage == BUFFERDATAUSAGE_STAGING && (indexbuffer || vertexbuffer || texelbuffer || storagebuffer))
throw love::Exception("Buffers created with 'staging' data usage cannot be index, vertex, texel, or shaderstorage buffer types.");
if (dataUsage == BUFFERDATAUSAGE_READBACK && (indexbuffer || vertexbuffer || texelbuffer || storagebuffer))
throw love::Exception("Buffers created with 'readback' data usage cannot be index, vertex, texel, or shaderstorage buffer types.");
size_t offset = 0;
size_t stride = 0;
+5 -4
View File
@@ -53,6 +53,7 @@ public:
enum MapType
{
MAP_WRITE_INVALIDATE,
MAP_READ_ONLY,
};
struct DataDeclaration
@@ -128,7 +129,7 @@ public:
/**
* Fill a portion of the buffer with data.
*/
virtual void fill(size_t offset, size_t size, const void *data) = 0;
virtual bool fill(size_t offset, size_t size, const void *data) = 0;
/**
* Copy a portion of this Buffer's data to another buffer, using the GPU.
@@ -147,10 +148,10 @@ public:
{
public:
Mapper(Buffer &buffer)
Mapper(Buffer &buffer, MapType maptype = MAP_WRITE_INVALIDATE)
: buffer(buffer)
{
data = buffer.map(MAP_WRITE_INVALIDATE, 0, buffer.getSize());
data = buffer.map(maptype, 0, buffer.getSize());
}
~Mapper()
@@ -179,7 +180,7 @@ protected:
BufferDataUsage dataUsage;
bool mapped;
MapType mappedType;
bool immutable;
}; // Buffer
+166 -1
View File
@@ -240,6 +240,9 @@ Graphics::~Graphics()
for (int i = 0; i < (int) SHADERSTAGE_MAX_ENUM; i++)
cachedShaderStages[i].clear();
pendingReadbacks.clear();
clearTemporaryResources();
Shader::deinitialize();
}
@@ -438,6 +441,46 @@ love::graphics::Text *Graphics::newText(graphics::Font *font, const std::vector<
return new Text(font, text);
}
love::data::ByteData *Graphics::readbackBuffer(Buffer *buffer, size_t offset, size_t size, data::ByteData *dest, size_t destoffset)
{
StrongRef<GraphicsReadback> readback;
readback.set(newReadbackInternal(READBACK_IMMEDIATE, buffer, offset, size, dest, destoffset), Acquire::NORETAIN);
auto data = readback->getBufferData();
if (data == nullptr)
throw love::Exception("love.graphics.readbackBuffer failed.");
data->retain();
return data;
}
GraphicsReadback *Graphics::readbackBufferAsync(Buffer *buffer, size_t offset, size_t size, data::ByteData *dest, size_t destoffset)
{
auto readback = newReadbackInternal(READBACK_ASYNC, buffer, offset, size, dest, destoffset);
pendingReadbacks.push_back(readback);
return readback;
}
image::ImageData *Graphics::readbackTexture(Texture *texture, int slice, int mipmap, const Rect &rect, image::ImageData *dest, int destx, int desty)
{
StrongRef<GraphicsReadback> readback;
readback.set(newReadbackInternal(READBACK_IMMEDIATE, texture, slice, mipmap, rect, dest, destx, desty), Acquire::NORETAIN);
auto imagedata = readback->getImageData();
if (imagedata == nullptr)
throw love::Exception("love.graphics.readbackTexture failed.");
imagedata->retain();
return imagedata;
}
GraphicsReadback *Graphics::readbackTextureAsync(Texture *texture, int slice, int mipmap, const Rect &rect, image::ImageData *dest, int destx, int desty)
{
auto readback = newReadbackInternal(READBACK_ASYNC, texture, slice, mipmap, rect, dest, destx, desty);
pendingReadbacks.push_back(readback);
return readback;
}
void Graphics::cleanupCachedShaderStage(ShaderStageType type, const std::string &hashkey)
{
cachedShaderStages[type].erase(hashkey);
@@ -893,6 +936,10 @@ void Graphics::setRenderTargets(const RenderTargets &rts)
realRTs.depthStencil.texture = getTemporaryTexture(dsformat, pixelw, pixelh, reqmsaa);
realRTs.depthStencil.slice = 0;
// TODO: fix this to call release at the right time.
// This only works here because nothing else calls getTemporaryTexture.
releaseTemporaryTexture(realRTs.depthStencil.texture);
setRenderTargetsInternal(realRTs, pixelw, pixelh, hasSRGBtexture);
}
else
@@ -995,12 +1042,15 @@ Texture *Graphics::getTemporaryTexture(PixelFormat format, int w, int h, int sam
for (TemporaryTexture &temp : temporaryTextures)
{
if (temp.framesSinceUse < 0)
continue;
Texture *c = temp.texture;
if (c->getPixelFormat() == format && c->getPixelWidth() == w
&& c->getPixelHeight() == h && c->getRequestedMSAA() == samples)
{
texture = c;
temp.framesSinceUse = 0;
temp.framesSinceUse = -1;
break;
}
}
@@ -1022,6 +1072,115 @@ Texture *Graphics::getTemporaryTexture(PixelFormat format, int w, int h, int sam
return texture;
}
void Graphics::releaseTemporaryTexture(Texture *texture)
{
for (TemporaryTexture &temp : temporaryTextures)
{
if (temp.texture == texture)
{
temp.framesSinceUse = 0;
break;
}
}
}
Buffer *Graphics::getTemporaryBuffer(size_t size, DataFormat format, uint32 usageflags, BufferDataUsage datausage)
{
Buffer *buffer = nullptr;
for (TemporaryBuffer &temp : temporaryBuffers)
{
if (temp.framesSinceUse < 0)
continue;
Buffer *b = temp.buffer;
if (temp.size == size && b->getDataMember(0).decl.format == format
&& b->getUsageFlags() == usageflags && b->getDataUsage() == datausage)
{
buffer = b;
temp.framesSinceUse = -1;
break;
}
}
if (buffer == nullptr)
{
Buffer::Settings settings(usageflags, datausage);
buffer = newBuffer(settings, format, nullptr, size, 0);
temporaryBuffers.emplace_back(buffer, size);
}
return buffer;
}
void Graphics::releaseTemporaryBuffer(Buffer *buffer)
{
for (TemporaryBuffer &temp : temporaryBuffers)
{
if (temp.buffer == buffer)
{
temp.framesSinceUse = 0;
break;
}
}
}
void Graphics::updateTemporaryResources()
{
for (int i = (int) temporaryTextures.size() - 1; i >= 0; i--)
{
auto &t = temporaryTextures[i];
if (t.framesSinceUse >= MAX_TEMPORARY_RESOURCE_UNUSED_FRAMES)
{
t.texture->release();
t = temporaryTextures.back();
temporaryTextures.pop_back();
}
else if (t.framesSinceUse >= 0)
t.framesSinceUse++;
}
for (int i = (int) temporaryBuffers.size() - 1; i >= 0; i--)
{
auto &t = temporaryBuffers[i];
if (t.framesSinceUse >= MAX_TEMPORARY_RESOURCE_UNUSED_FRAMES)
{
t.buffer->release();
t = temporaryBuffers.back();
temporaryBuffers.pop_back();
}
else if (t.framesSinceUse >= 0)
t.framesSinceUse++;
}
}
void Graphics::clearTemporaryResources()
{
for (auto temp :temporaryBuffers)
temp.buffer->release();
for (auto temp : temporaryTextures)
temp.texture->release();
temporaryBuffers.clear();
temporaryTextures.clear();
}
void Graphics::updatePendingReadbacks()
{
for (int i = (int)pendingReadbacks.size() - 1; i >= 0; i--)
{
pendingReadbacks[i]->update();
if (pendingReadbacks[i]->isComplete())
{
pendingReadbacks[i] = pendingReadbacks.back();
pendingReadbacks.pop_back();
}
}
}
void Graphics::intersectScissor(const Rect &rect)
{
Rect currect = states.back().scissorRect;
@@ -1187,6 +1346,9 @@ void Graphics::copyBuffer(Buffer *source, Buffer *dest, size_t sourceoffset, siz
if (dest->getDataUsage() == BUFFERDATAUSAGE_STREAM)
throw love::Exception("Buffers created with 'stream' data usage cannot be used as a copy destination.");
if (source->getDataUsage() == BUFFERDATAUSAGE_READBACK)
throw love::Exception("Buffers created with 'readback' data usage cannot be used as a copy source.");
if (sourcerange.getMax() >= source->getSize())
throw love::Exception("Buffer copy source offset and size doesn't fit within the source Buffer's size.");
@@ -1296,6 +1458,9 @@ void Graphics::copyBufferToTexture(Buffer *source, Texture *dest, size_t sourceo
if (!capabilities.features[FEATURE_COPY_BUFFER_TO_TEXTURE])
throw love::Exception("Copying a Buffer to a Texture is not supported on this system.");
if (source->getDataUsage() == BUFFERDATAUSAGE_READBACK)
throw love::Exception("Buffers created with 'readback' data usage cannot be used as a copy source.");
PixelFormat format = dest->getPixelFormat();
if (isPixelFormatDepthStencil(format))
+37 -3
View File
@@ -37,6 +37,7 @@
#include "Shader.h"
#include "Quad.h"
#include "Mesh.h"
#include "GraphicsReadback.h"
#include "Deprecations.h"
#include "renderstate.h"
#include "math/Transform.h"
@@ -461,6 +462,12 @@ public:
Text *newText(Font *font, const std::vector<Font::ColoredString> &text = {});
data::ByteData *readbackBuffer(Buffer *buffer, size_t offset, size_t size, data::ByteData *dest, size_t destoffset);
GraphicsReadback *readbackBufferAsync(Buffer *buffer, size_t offset, size_t size, data::ByteData *dest, size_t destoffset);
image::ImageData *readbackTexture(Texture *texture, int slice, int mipmap, const Rect &rect, image::ImageData *dest, int destx, int desty);
GraphicsReadback *readbackTextureAsync(Texture *texture, int slice, int mipmap, const Rect &rect, image::ImageData *dest, int destx, int desty);
bool validateShader(bool gles, const std::vector<std::string> &stages, const Shader::CompileOptions &options, std::string &err);
/**
@@ -857,6 +864,12 @@ public:
static void flushBatchedDrawsGlobal();
Texture *getTemporaryTexture(PixelFormat format, int w, int h, int samples);
void releaseTemporaryTexture(Texture *texture);
Buffer *getTemporaryBuffer(size_t size, DataFormat format, uint32 usageflags, BufferDataUsage datausage);
void releaseTemporaryBuffer(Buffer *buffer);
void cleanupCachedShaderStage(ShaderStageType type, const std::string &cachekey);
template <typename T>
@@ -955,6 +968,19 @@ protected:
}
};
struct TemporaryBuffer
{
Buffer *buffer;
size_t size;
int framesSinceUse;
TemporaryBuffer(Buffer *buf, size_t size)
: buffer(buf)
, size(size)
, framesSinceUse(-1)
{}
};
struct TemporaryTexture
{
Texture *texture;
@@ -962,7 +988,7 @@ protected:
TemporaryTexture(Texture *tex)
: texture(tex)
, framesSinceUse(0)
, framesSinceUse(-1)
{}
};
@@ -971,6 +997,9 @@ protected:
virtual Shader *newShaderInternal(StrongRef<ShaderStage> stages[SHADERSTAGE_MAX_ENUM]) = 0;
virtual StreamBuffer *newStreamBuffer(BufferUsage type, size_t size) = 0;
virtual GraphicsReadback *newReadbackInternal(ReadbackMethod method, Buffer *buffer, size_t offset, size_t size, data::ByteData *dest, size_t destoffset) = 0;
virtual GraphicsReadback *newReadbackInternal(ReadbackMethod method, Texture *texture, int slice, int mipmap, const Rect &rect, image::ImageData *dest, int destx, int desty) = 0;
virtual bool dispatch(int x, int y, int z) = 0;
virtual void setRenderTargetsInternal(const RenderTargets &rts, int pixelw, int pixelh, bool hasSRGBtexture) = 0;
@@ -981,7 +1010,10 @@ protected:
void createQuadIndexBuffer();
void createFanIndexBuffer();
Texture *getTemporaryTexture(PixelFormat format, int w, int h, int samples);
void updateTemporaryResources();
void clearTemporaryResources();
void updatePendingReadbacks();
void restoreState(const DisplayState &s);
void restoreStateChecked(const DisplayState &s);
@@ -1004,6 +1036,7 @@ protected:
StrongRef<love::graphics::Font> defaultFont;
std::vector<ScreenshotInfo> pendingScreenshotCallbacks;
std::vector<StrongRef<GraphicsReadback>> pendingReadbacks;
BatchedDrawState batchedDrawState;
@@ -1015,6 +1048,7 @@ protected:
std::vector<DisplayState> states;
std::vector<StackType> stackTypeStack;
std::vector<TemporaryBuffer> temporaryBuffers;
std::vector<TemporaryTexture> temporaryTextures;
int renderTargetSwitchCount;
@@ -1029,7 +1063,7 @@ protected:
Deprecations deprecations;
static const size_t MAX_USER_STACK_DEPTH = 128;
static const int MAX_TEMPORARY_TEXTURE_UNUSED_FRAMES = 16;
static const int MAX_TEMPORARY_RESOURCE_UNUSED_FRAMES = 16;
private:
+229
View File
@@ -0,0 +1,229 @@
/**
* Copyright (c) 2006-2022 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
**/
#include "GraphicsReadback.h"
#include "Buffer.h"
#include "Texture.h"
#include "Graphics.h"
#include "data/ByteData.h"
#include "image/ImageData.h"
#include "image/Image.h"
namespace love
{
namespace graphics
{
love::Type GraphicsReadback::type("GraphicsReadback", &Object::type);
GraphicsReadback::GraphicsReadback(Graphics *gfx, ReadbackMethod method, Buffer *buffer, size_t offset, size_t size, love::data::ByteData *dest, size_t destoffset)
: dataType(DATA_BUFFER)
, method(method)
, bufferData(dest)
{
const auto &caps = gfx->getCapabilities();
if (!caps.features[Graphics::FEATURE_COPY_BUFFER])
throw love::Exception("readbackBuffer is not supported on this system (buffer copy support is required).");
if (offset + size > buffer->getSize())
throw love::Exception("Invalid offset or size for the given Buffer.");
if (dest != nullptr && destoffset + size > dest->getSize())
throw love::Exception("Invalid destination offset or size for the given ByteData.");
bufferDataOffset = dest != nullptr ? destoffset : 0;
}
GraphicsReadback::GraphicsReadback(Graphics *gfx, ReadbackMethod method, Texture *texture, int slice, int mipmap, const Rect &rect, love::image::ImageData *dest, int destx, int desty)
: dataType(DATA_TEXTURE)
, method(method)
, imageData(dest)
, rect(rect)
{
const auto &caps = gfx->getCapabilities();
if (gfx->isRenderTargetActive(texture))
throw love::Exception("readbackTexture cannot be called while that Texture is an active render target.");
if (!texture->isReadable())
throw love::Exception("readbackTexture requires a readable Texture.");
int tw = texture->getPixelWidth(mipmap);
int th = texture->getPixelHeight(mipmap);
auto texType = texture->getTextureType();
if (rect.x < 0 || rect.y < 0 || rect.w <= 0 || rect.h <= 0 || (rect.x + rect.w) > tw || (rect.y + rect.h) > th)
throw love::Exception("Invalid rectangle dimensions.");
if (slice < 0 || (texType == TEXTURE_VOLUME && slice >= texture->getDepth(mipmap))
|| (texType == TEXTURE_2D_ARRAY && slice >= texture->getLayerCount())
|| (texType == TEXTURE_CUBE && slice >= 6))
{
throw love::Exception("Invalid slice index.");
}
textureFormat = getLinearPixelFormat(texture->getPixelFormat());
if (!image::ImageData::validPixelFormat(textureFormat))
{
const char *formatname = "unknown";
love::getConstant(textureFormat, formatname);
throw love::Exception("ImageData with the '%s' pixel format is not supported.", formatname);
}
bool isRT = texture->isRenderTarget();
if (method == READBACK_ASYNC)
{
if (isRT && !caps.features[Graphics::FEATURE_COPY_RENDER_TARGET_TO_BUFFER])
throw love::Exception("readbackTextureAsync is not supported on this system.");
else if (!isRT && !caps.features[Graphics::FEATURE_COPY_TEXTURE_TO_BUFFER])
throw love::Exception("readbackTextureAsync a with non-render-target textures is not supported on this system.");
}
else
{
if (!isRT && !caps.features[Graphics::FEATURE_COPY_TEXTURE_TO_BUFFER])
throw love::Exception("readbackTexture with a non-render-target texture is not supported on this system.");
}
if (dest != nullptr)
{
if (dest->getFormat() != textureFormat)
throw love::Exception("Destination ImageData pixel format must match the source Texture's format.");
if (destx < 0 || desty < 0)
throw love::Exception("Invalid destination ImageData x/y coordinates.");
if (destx + rect.w > dest->getWidth() || desty + rect.h > dest->getHeight())
throw love::Exception("The specified rectangle does not fit within the destination ImageData's dimensions.");
}
imageDataX = dest != nullptr ? destx : 0;
imageDataY = dest != nullptr ? desty : 0;
}
GraphicsReadback::~GraphicsReadback()
{
}
love::data::ByteData *GraphicsReadback::getBufferData() const
{
if (!isComplete())
return nullptr;
return bufferData;
}
love::image::ImageData *GraphicsReadback::getImageData() const
{
if (!isComplete())
return nullptr;
return imageData;
}
void *GraphicsReadback::prepareReadbackDest(size_t size)
{
if (dataType == DATA_TEXTURE)
{
if (imageData.get())
{
// Not the cleanest, but should work since uncompressed formats always
// have 1x1 blocks.
int pixels = imageDataY * imageData->getWidth() + imageDataX;
size_t offset = getPixelFormatUncompressedRowSize(textureFormat, pixels);
return (uint8 *) imageData->getData() + offset;
}
else
{
auto module = Module::getInstance<image::Image>(Module::M_IMAGE);
if (module == nullptr)
throw love::Exception("The love.image module must be loaded for readbackTexture.");
imageData.set(module->newImageData(rect.w, rect.h, textureFormat, nullptr), Acquire::NORETAIN);
return imageData->getData();
}
}
else
{
if (!bufferData.get())
bufferData.set(new love::data::ByteData(size, false), Acquire::NORETAIN);
return (uint8 *) bufferData->getData() + bufferDataOffset;
}
}
GraphicsReadback::Status GraphicsReadback::readbackBuffer(Buffer *buffer, size_t offset, size_t size)
{
if (buffer == nullptr)
return STATUS_ERROR;
const void *data = buffer->map(Buffer::MAP_READ_ONLY, offset, size);
if (data == nullptr)
return STATUS_ERROR;
bool success = true;
try
{
void *dest = prepareReadbackDest(size);
if (dest == nullptr)
return STATUS_ERROR;
if (imageData.get())
{
love::thread::Lock lock(imageData->getMutex());
if (imageData->getWidth() != rect.w)
{
// Readback of compressed textures into ImageData isn't supported,
// so this is fine.
size_t stride = getPixelFormatUncompressedRowSize(textureFormat, imageData->getWidth());
size_t rowsize = getPixelFormatUncompressedRowSize(textureFormat, rect.w);
for (int i = 0; i < rect.h; i++)
{
memcpy(dest, data, rowsize);
dest = (uint8 *) dest + stride;
data = (uint8 *) data + rowsize;
}
}
else
{
memcpy(dest, data, std::min(size, imageData->getSize()));
}
}
else
{
memcpy(dest, data, std::min(size, bufferData->getSize()));
}
}
catch (love::Exception &)
{
success = false;
}
buffer->unmap(offset, size);
return success ? STATUS_COMPLETE : STATUS_ERROR;
}
} // graphics
} // love
+112
View File
@@ -0,0 +1,112 @@
/**
* Copyright (c) 2006-2022 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
**/
#pragma once
// LOVE
#include "common/config.h"
#include "common/int.h"
#include "common/math.h"
#include "common/Object.h"
#include "common/StringMap.h"
#include "common/pixelformat.h"
namespace love::image
{
class ImageData;
class CompressedImageData;
}
namespace love::data
{
class ByteData;
}
namespace love
{
namespace graphics
{
class Buffer;
class Texture;
class Graphics;
enum ReadbackMethod
{
READBACK_IMMEDIATE,
READBACK_ASYNC,
};
class GraphicsReadback : public love::Object
{
public:
enum Status
{
STATUS_WAITING,
STATUS_COMPLETE,
STATUS_ERROR,
STATUS_MAX_ENUM
};
static love::Type type;
GraphicsReadback(Graphics *gfx, ReadbackMethod method, Buffer *buffer, size_t offset, size_t size, love::data::ByteData *dest, size_t destoffset);
GraphicsReadback(Graphics *gfx, ReadbackMethod method, Texture *texture, int slice, int mipmap, const Rect &rect, love::image::ImageData *dest, int destx, int desty);
virtual ~GraphicsReadback();
virtual void wait() = 0;
virtual void update() = 0;
bool isComplete() const { return status != STATUS_WAITING; }
ReadbackMethod getMethod() const { return method; }
bool hasError() const { return status == STATUS_ERROR; }
love::data::ByteData *getBufferData() const;
love::image::ImageData *getImageData() const;
protected:
enum DataType
{
DATA_BUFFER,
DATA_TEXTURE,
};
void *prepareReadbackDest(size_t size);
Status readbackBuffer(Buffer *buffer, size_t offset, size_t size);
DataType dataType;
ReadbackMethod method;
Status status = STATUS_WAITING;
StrongRef<love::data::ByteData> bufferData;
size_t bufferDataOffset = 0;
StrongRef<love::image::ImageData> imageData;
Rect rect = {};
PixelFormat textureFormat = PIXELFORMAT_UNKNOWN;
int imageDataX = 0;
int imageDataY = 0;
}; // GraphicsReadback
} // graphics
} // love
-41
View File
@@ -563,47 +563,6 @@ void Texture::generateMipmaps()
generateMipmapsInternal();
}
love::image::ImageData *Texture::newImageData(love::image::Image *module, int slice, int mipmap, const Rect &r)
{
if (!isReadable())
throw love::Exception("Texture:newImageData cannot be called on non-readable Textures.");
if (!isRenderTarget())
throw love::Exception("Texture:newImageData can only be called on render target Textures.");
if (isPixelFormatDepthStencil(getPixelFormat()))
throw love::Exception("Texture:newImageData cannot be called on Textures with depth/stencil pixel formats.");
if (r.x < 0 || r.y < 0 || r.w <= 0 || r.h <= 0 || (r.x + r.w) > getPixelWidth(mipmap) || (r.y + r.h) > getPixelHeight(mipmap))
throw love::Exception("Invalid rectangle dimensions.");
if (slice < 0 || (texType == TEXTURE_VOLUME && slice >= getDepth(mipmap))
|| (texType == TEXTURE_2D_ARRAY && slice >= layers)
|| (texType == TEXTURE_CUBE && slice >= 6))
{
throw love::Exception("Invalid slice index.");
}
Graphics *gfx = Module::getInstance<Graphics>(Module::M_GRAPHICS);
if (gfx != nullptr && gfx->isRenderTargetActive(this))
throw love::Exception("Texture:newImageData cannot be called while that Texture is an active render target.");
PixelFormat dataformat = getLinearPixelFormat(getPixelFormat());
if (!image::ImageData::validPixelFormat(dataformat))
{
const char *formatname = "unknown";
love::getConstant(dataformat, formatname);
throw love::Exception("ImageData with the '%s' pixel format is not supported.", formatname);
}
auto imagedata = module->newImageData(r.w, r.h, dataformat);
readbackImageData(imagedata, slice, mipmap, r);
return imagedata;
}
TextureType Texture::getTextureType() const
{
return texType;
-3
View File
@@ -245,8 +245,6 @@ public:
void generateMipmaps();
love::image::ImageData *newImageData(love::image::Image *module, int slice, int mipmap, const Rect &rect);
virtual void copyFromBuffer(Buffer *source, size_t sourceoffset, int sourcewidth, size_t size, int slice, int mipmap, const Rect &rect) = 0;
virtual void copyToBuffer(Buffer *dest, int slice, int mipmap, const Rect &rect, size_t destoffset, int destwidth, size_t size) = 0;
@@ -313,7 +311,6 @@ protected:
bool supportsGenerateMipmaps(const char *&outReason) const;
virtual void generateMipmapsInternal() = 0;
virtual void readbackImageData(love::image::ImageData *imagedata, int slice, int mipmap, const Rect &rect) = 0;
bool validateDimensions(bool throwException) const;
+1 -1
View File
@@ -40,7 +40,7 @@ public:
void *map(MapType map, size_t offset, size_t size) override;
void unmap(size_t usedoffset, size_t usedsize) override;
void fill(size_t offset, size_t size, const void *data) override;
bool fill(size_t offset, size_t size, const void *data) override;
void copyTo(love::graphics::Buffer *dest, size_t sourceoffset, size_t destoffset, size_t size) override;
ptrdiff_t getHandle() const override { return (ptrdiff_t) buffer; }
+38 -24
View File
@@ -18,7 +18,7 @@
* 3. This notice may not be removed or altered from any source distribution.
**/
#import "Buffer.h"
#include "Buffer.h"
#include "Graphics.h"
namespace love
@@ -65,11 +65,16 @@ Buffer::Buffer(love::graphics::Graphics *gfx, id<MTLDevice> device, const Settin
size = getSize();
arraylength = getArrayLength();
MTLResourceOptions opts = MTLResourceStorageModePrivate;
MTLResourceOptions opts = 0;
if (settings.dataUsage == BUFFERDATAUSAGE_READBACK)
opts |= MTLResourceStorageModeShared;
else
opts |= MTLResourceStorageModePrivate;
buffer = [device newBufferWithLength:size options:opts];
if (buffer == nil)
throw love::Exception("Could not create buffer (out of VRAM?)");
throw love::Exception("Could not create buffer with %d bytes (out of VRAM?)", size);
if (usageFlags & BUFFERUSAGEFLAG_TEXEL)
{
@@ -109,16 +114,30 @@ Buffer::~Buffer()
texture = nil;
}}
void *Buffer::map(MapType /*map*/, size_t offset, size_t size)
void *Buffer::map(MapType map, size_t offset, size_t size)
{ @autoreleasepool {
if (size == 0 || isImmutable())
if (size == 0)
return nullptr;
if (map == MAP_WRITE_INVALIDATE && (isImmutable() || dataUsage == BUFFERDATAUSAGE_READBACK))
return nullptr;
if (map == MAP_READ_ONLY && dataUsage != BUFFERDATAUSAGE_READBACK)
return nullptr;
Range r(offset, size);
if (!Range(0, getSize()).contains(r))
return nullptr;
if (map == MAP_READ_ONLY)
{
mappedRange = r;
mapped = true;
mappedType = map;
return (char *) buffer.contents + offset;
}
auto gfx = Graphics::getInstance();
// TODO: Don't create a new buffer every time, also do something for stream
@@ -129,6 +148,7 @@ void *Buffer::map(MapType /*map*/, size_t offset, size_t size)
{
mappedRange = r;
mapped = true;
mappedType = map;
return mapBuffer.contents;
}
@@ -137,6 +157,12 @@ void *Buffer::map(MapType /*map*/, size_t offset, size_t size)
void Buffer::unmap(size_t usedoffset, size_t usedsize)
{ @autoreleasepool {
if (mappedType == MAP_READ_ONLY)
{
mapped = false;
return;
}
if (mapBuffer == nil)
return;
@@ -158,29 +184,17 @@ void Buffer::unmap(size_t usedoffset, size_t usedsize)
mapped = false;
}}
void Buffer::fill(size_t offset, size_t size, const void *data)
bool Buffer::fill(size_t offset, size_t size, const void *data)
{ @autoreleasepool {
if (size == 0 || isImmutable())
return;
void *dest = map(MAP_WRITE_INVALIDATE, offset, size);
size_t buffersize = getSize();
if (dest == nullptr)
return false;
if (!Range(0, buffersize).contains(Range(offset, size)))
return;
memcpy(dest, data, size);
// TODO: Don't create a new buffer every time, also do something for stream
// buffers.
auto gfx = Graphics::getInstance();
auto encoder = gfx->useBlitEncoder();
auto tempbuffer = [gfx->device newBufferWithLength:size options:MTLResourceStorageModeShared];
memcpy(tempbuffer.contents, data, size);
[encoder copyFromBuffer:tempbuffer
sourceOffset:0
toBuffer:buffer
destinationOffset:offset
size:size];
unmap(offset, size);
return true;
}}
void Buffer::copyTo(love::graphics::Buffer *dest, size_t sourceoffset, size_t destoffset, size_t size)
+4
View File
@@ -199,6 +199,10 @@ private:
love::graphics::ShaderStage *newShaderStageInternal(ShaderStageType stage, const std::string &cachekey, const std::string &source, bool gles) override;
love::graphics::Shader *newShaderInternal(StrongRef<love::graphics::ShaderStage> stages[SHADERSTAGE_MAX_ENUM]) override;
love::graphics::StreamBuffer *newStreamBuffer(BufferUsage usage, size_t size) override;
love::graphics::GraphicsReadback *newReadbackInternal(ReadbackMethod method, love::graphics::Buffer *buffer, size_t offset, size_t size, data::ByteData *dest, size_t destoffset) override;
love::graphics::GraphicsReadback *newReadbackInternal(ReadbackMethod method, love::graphics::Texture *texture, int slice, int mipmap, const Rect &rect, image::ImageData *dest, int destx, int desty) override;
void setRenderTargetsInternal(const RenderTargets &rts, int pixelw, int pixelh, bool hasSRGBcanvas) override;
void initCapabilities() override;
void getAPIStats(int &shaderswitches) const override;
+14 -16
View File
@@ -22,6 +22,7 @@
#include "StreamBuffer.h"
#include "Buffer.h"
#include "Texture.h"
#include "GraphicsReadback.h"
#include "Shader.h"
#include "ShaderStage.h"
#include "window/Window.h"
@@ -418,6 +419,16 @@ love::graphics::Buffer *Graphics::newBuffer(const Buffer::Settings &settings, co
return new Buffer(this, device, settings, format, data, size, arraylength);
}
love::graphics::GraphicsReadback *Graphics::newReadbackInternal(ReadbackMethod method, love::graphics::Buffer *buffer, size_t offset, size_t size, data::ByteData *dest, size_t destoffset)
{
return new GraphicsReadback(this, method, buffer, offset, size, dest, destoffset);
}
love::graphics::GraphicsReadback *Graphics::newReadbackInternal(ReadbackMethod method, love::graphics::Texture *texture, int slice, int mipmap, const Rect &rect, image::ImageData *dest, int destx, int desty)
{
return new GraphicsReadback(this, method, texture, slice, mipmap, rect, dest, destx, desty);
}
Matrix4 Graphics::computeDeviceProjection(const Matrix4 &projection, bool /*rendertotexture*/) const
{
uint32 flags = DEVICE_PROJECTION_FLIP_Y;
@@ -501,10 +512,7 @@ void Graphics::unSetMode()
submitCommandBuffer(SUBMIT_DONE);
for (auto temp : temporaryTextures)
temp.texture->release();
temporaryTextures.clear();
clearTemporaryResources();
created = false;
metalLayer = nil;
@@ -1620,18 +1628,8 @@ void Graphics::present(void *screenshotCallbackData)
renderTargetSwitchCount = 0;
drawCallsBatched = 0;
// This assumes temporary canvases will only be used within a render pass.
for (int i = (int) temporaryTextures.size() - 1; i >= 0; i--)
{
if (temporaryTextures[i].framesSinceUse >= MAX_TEMPORARY_TEXTURE_UNUSED_FRAMES)
{
temporaryTextures[i].texture->release();
temporaryTextures[i] = temporaryTextures.back();
temporaryTextures.pop_back();
}
else
temporaryTextures[i].framesSinceUse++;
}
updatePendingReadbacks();
updateTemporaryResources();
}}
int Graphics::getRequestedBackbufferMSAA() const
@@ -0,0 +1,54 @@
/**
* Copyright (c) 2006-2022 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
**/
#pragma once
// LOVE
#include "graphics/GraphicsReadback.h"
#include "common/math.h"
#include <atomic>
#import <Metal/MTLCommandBuffer.h>
namespace love::graphics::metal
{
class GraphicsReadback final : public love::graphics::GraphicsReadback
{
public:
GraphicsReadback(love::graphics::Graphics *gfx, ReadbackMethod method, love::graphics::Buffer *buffer, size_t offset, size_t size, data::ByteData *dest, size_t destoffset);
GraphicsReadback(love::graphics::Graphics *gfx, ReadbackMethod method, love::graphics::Texture *texture, int slice, int mipmap, const Rect &rect, image::ImageData *dest, int destx, int desty);
virtual ~GraphicsReadback();
void wait() override;
void update() override;
private:
id<MTLCommandBuffer> cmd;
std::atomic_bool done;
StrongRef<love::graphics::Buffer> stagingBuffer;
}; // GraphicsReadback
} // love::graphics::metal
@@ -0,0 +1,143 @@
/**
* Copyright (c) 2006-2022 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
**/
#include "GraphicsReadback.h"
#include "Buffer.h"
#include "Texture.h"
#include "Graphics.h"
#include "data/ByteData.h"
namespace love::graphics::metal
{
GraphicsReadback::GraphicsReadback(love::graphics::Graphics *gfx, ReadbackMethod method, love::graphics::Buffer *buffer, size_t offset, size_t size, data::ByteData *dest, size_t destoffset)
: love::graphics::GraphicsReadback(gfx, method, buffer, offset, size, dest, destoffset)
, done(false)
{ @autoreleasepool {
auto mgfx = (Graphics *) gfx;
// Immediate readback of readback-type buffers doesn't need a staging buffer.
if (method != READBACK_IMMEDIATE || buffer->getDataUsage() != BUFFERDATAUSAGE_READBACK)
{
stagingBuffer = gfx->getTemporaryBuffer(size, DATAFORMAT_FLOAT, 0, BUFFERDATAUSAGE_READBACK);
gfx->copyBuffer(buffer, stagingBuffer, offset, 0, size);
}
// use instead of get, in case this was the first command in the frame.
cmd = mgfx->useCommandBuffer();
auto pthis = this;
pthis->retain();
[cmd addCompletedHandler:^(id<MTLCommandBuffer> _Nonnull)
{
pthis->done = true;
pthis->release();
}];
if (method == READBACK_IMMEDIATE)
{
wait();
if (stagingBuffer.get())
{
status = readbackBuffer(stagingBuffer, 0, size);
gfx->releaseTemporaryBuffer(stagingBuffer);
}
else
{
status = readbackBuffer(buffer, offset, size);
}
}
}}
GraphicsReadback::GraphicsReadback(love::graphics::Graphics *gfx, ReadbackMethod method, love::graphics::Texture *texture, int slice, int mipmap, const Rect &rect, image::ImageData *dest, int destx, int desty)
: love::graphics::GraphicsReadback(gfx, method, texture, slice, mipmap, rect, dest, destx, desty)
, done(false)
{ @autoreleasepool {
auto mgfx = (Graphics *) gfx;
size_t size = getPixelFormatSliceSize(textureFormat, rect.w, rect.h);
stagingBuffer = gfx->getTemporaryBuffer(size, DATAFORMAT_FLOAT, 0, BUFFERDATAUSAGE_READBACK);
gfx->copyTextureToBuffer(texture, stagingBuffer, slice, mipmap, rect, 0, 0);
cmd = mgfx->getCommandBuffer();
auto pthis = this;
pthis->retain();
[cmd addCompletedHandler:^(id<MTLCommandBuffer> _Nonnull)
{
pthis->done = true;
pthis->release();
}];
if (method == READBACK_IMMEDIATE)
{
wait();
status = readbackBuffer(stagingBuffer, 0, size);
gfx->releaseTemporaryBuffer(stagingBuffer);
}
}}
GraphicsReadback::~GraphicsReadback()
{ @autoreleasepool {
cmd = nil;
}}
void GraphicsReadback::wait()
{ @autoreleasepool {
if (status != STATUS_WAITING || cmd == nil)
return;
if (cmd.status == MTLCommandBufferStatusNotEnqueued)
{
auto gfx = Graphics::getInstance();
gfx->submitCommandBuffer(Graphics::SUBMIT_STORE);
}
[cmd waitUntilCompleted];
cmd = nil;
update();
}}
void GraphicsReadback::update()
{
if (status != STATUS_WAITING)
return;
if (done)
{
if (stagingBuffer.get())
status = readbackBuffer(stagingBuffer, 0, stagingBuffer->getSize());
else
status = STATUS_ERROR;
if (stagingBuffer.get())
{
auto gfx = Module::getInstance<love::graphics::Graphics>(Module::M_GRAPHICS);
if (gfx != nullptr)
gfx->releaseTemporaryBuffer(stagingBuffer);
stagingBuffer.set(nullptr);
}
}
}
} // love::graphics::metal
-1
View File
@@ -57,7 +57,6 @@ private:
void uploadByteData(PixelFormat pixelformat, const void *data, size_t size, int level, int slice, const Rect &r) override;
void generateMipmapsInternal() override;
void readbackImageData(love::image::ImageData *imagedata, int slice, int mipmap, const Rect &rect) override;
id<MTLTexture> texture;
id<MTLTexture> msaaTexture;
-46
View File
@@ -273,52 +273,6 @@ void Texture::generateMipmapsInternal()
[encoder generateMipmapsForTexture:texture];
}}
void Texture::readbackImageData(love::image::ImageData *imagedata, int slice, int mipmap, const Rect &rect)
{ @autoreleasepool {
auto gfx = Graphics::getInstance();
id<MTLBlitCommandEncoder> encoder = gfx->useBlitEncoder();
size_t rowSize = 0;
if (isCompressed())
rowSize = getPixelFormatCompressedBlockRowSize(format, rect.w);
else
rowSize = getPixelFormatUncompressedRowSize(format, rect.w);
// TODO: Verify this is correct for compressed formats at small sizes.
// TODO: make sure this is consistent with the imagedata byte size?
size_t sliceSize = getPixelFormatSliceSize(format, rect.w, rect.h);
int z = texType == TEXTURE_VOLUME ? slice : 0;
id<MTLBuffer> buffer = [gfx->device newBufferWithLength:sliceSize
options:MTLResourceStorageModeShared];
MTLBlitOption options = MTLBlitOptionNone;
if (isPixelFormatDepthStencil(format))
options = MTLBlitOptionDepthFromDepthStencil;
[encoder copyFromTexture:texture
sourceSlice:texType == TEXTURE_VOLUME ? 0 : slice
sourceLevel:mipmap
sourceOrigin:MTLOriginMake(rect.x, rect.y, z)
sourceSize:MTLSizeMake(rect.w, rect.h, 1)
toBuffer:buffer
destinationOffset:0
destinationBytesPerRow:rowSize
destinationBytesPerImage:sliceSize
options:options];
id<MTLCommandBuffer> cmd = gfx->getCommandBuffer();
gfx->submitBlitEncoder();
gfx->submitCommandBuffer(Graphics::SUBMIT_STORE);
[cmd waitUntilCompleted];
memcpy(imagedata->getData(), buffer.contents, imagedata->getSize());
}}
void Texture::copyFromBuffer(love::graphics::Buffer *source, size_t sourceoffset, int sourcewidth, size_t size, int slice, int mipmap, const Rect &rect)
{ @autoreleasepool {
id<MTLBlitCommandEncoder> encoder = Graphics::getInstance()->useBlitEncoder();
+35 -8
View File
@@ -103,7 +103,7 @@ Buffer::Buffer(love::graphics::Graphics *gfx, const Settings &settings, const st
if (!load(data))
{
unloadVolatile();
throw love::Exception("Could not create buffer (out of VRAM?)");
throw love::Exception("Could not create buffer with %d bytes (out of VRAM?)", size);
}
}
@@ -164,11 +164,17 @@ bool Buffer::supportsOrphan() const
return dataUsage == BUFFERDATAUSAGE_STREAM || dataUsage == BUFFERDATAUSAGE_DYNAMIC;
}
void *Buffer::map(MapType /*map*/, size_t offset, size_t size)
void *Buffer::map(MapType map, size_t offset, size_t size)
{
if (size == 0 || isImmutable())
if (size == 0)
return nullptr;
if (map == MAP_WRITE_INVALIDATE && (isImmutable() || dataUsage == BUFFERDATAUSAGE_READBACK))
return nullptr;
if (map == MAP_READ_ONLY && dataUsage != BUFFERDATAUSAGE_READBACK)
return nullptr;
Range r(offset, size);
if (!Range(0, getSize()).contains(r))
@@ -176,7 +182,16 @@ void *Buffer::map(MapType /*map*/, size_t offset, size_t size)
char *data = nullptr;
if (ownsMemoryMap)
if (map == MAP_READ_ONLY)
{
gl.bindBuffer(mapUsage, buffer);
if (GLAD_VERSION_3_0 || GLAD_ES_VERSION_3_0)
data = (char *) glMapBufferRange(target, offset, size, GL_MAP_READ_BIT);
else if (GLAD_VERSION_1_1)
data = (char *) glMapBuffer(target, GL_READ_ONLY) + offset;
}
else if (ownsMemoryMap)
{
if (memoryMap == nullptr)
memoryMap = (char *) malloc(getSize());
@@ -191,6 +206,7 @@ void *Buffer::map(MapType /*map*/, size_t offset, size_t size)
if (data != nullptr)
{
mapped = true;
mappedType = map;
mappedRange = r;
if (!ownsMemoryMap)
memoryMap = data;
@@ -208,6 +224,15 @@ void Buffer::unmap(size_t usedoffset, size_t usedsize)
mapped = false;
if (mappedType == MAP_READ_ONLY)
{
gl.bindBuffer(mapUsage, buffer);
glUnmapBuffer(target);
if (!ownsMemoryMap)
memoryMap = nullptr;
return;
}
// Orphan optimization - see fill().
if (supportsOrphan() && mappedRange.first == 0 && mappedRange.getSize() == getSize())
{
@@ -227,15 +252,15 @@ void Buffer::unmap(size_t usedoffset, size_t usedsize)
}
}
void Buffer::fill(size_t offset, size_t size, const void *data)
bool Buffer::fill(size_t offset, size_t size, const void *data)
{
if (size == 0 || isImmutable())
return;
if (size == 0 || isImmutable() || dataUsage == BUFFERDATAUSAGE_READBACK)
return false;
size_t buffersize = getSize();
if (!Range(0, buffersize).contains(Range(offset, size)))
return;
return false;
GLenum gldatausage = OpenGL::getGLBufferDataUsage(dataUsage);
@@ -259,6 +284,8 @@ void Buffer::fill(size_t offset, size_t size, const void *data)
{
glBufferSubData(target, (GLintptr) offset, (GLsizeiptr) size, data);
}
return true;
}
void Buffer::copyTo(love::graphics::Buffer *dest, size_t sourceoffset, size_t destoffset, size_t size)
+3 -1
View File
@@ -52,12 +52,14 @@ public:
void *map(MapType map, size_t offset, size_t size) override;
void unmap(size_t usedoffset, size_t usedsize) override;
void fill(size_t offset, size_t size, const void *data) override;
bool fill(size_t offset, size_t size, const void *data) override;
void copyTo(love::graphics::Buffer *dest, size_t sourceoffset, size_t destoffset, size_t size) override;
ptrdiff_t getHandle() const override { return buffer; };
ptrdiff_t getTexelBufferHandle() const override { return texture; };
BufferUsage getMapUsage() const { return mapUsage; }
private:
bool load(const void *initialdata);
+16
View File
@@ -45,6 +45,22 @@ bool FenceSync::fence()
return !wasActive;
}
bool FenceSync::isComplete() const
{
if (sync == 0)
return true;
GLenum status = glClientWaitSync(sync, 0, 0);
if (status == GL_ALREADY_SIGNALED || status == GL_CONDITION_SATISFIED)
return true;
if (status == GL_WAIT_FAILED)
return true;
return false;
}
bool FenceSync::cpuWait()
{
if (sync == 0)
+1
View File
@@ -42,6 +42,7 @@ public:
~FenceSync();
bool fence();
bool isComplete() const;
bool cpuWait();
void cleanup();
+15 -16
View File
@@ -26,6 +26,7 @@
#include "Graphics.h"
#include "font/Font.h"
#include "StreamBuffer.h"
#include "GraphicsReadback.h"
#include "math/MathModule.h"
#include "window/Window.h"
#include "Buffer.h"
@@ -177,6 +178,16 @@ love::graphics::Buffer *Graphics::newBuffer(const Buffer::Settings &settings, co
return new Buffer(this, settings, format, data, size, arraylength);
}
love::graphics::GraphicsReadback *Graphics::newReadbackInternal(ReadbackMethod method, love::graphics::Buffer *buffer, size_t offset, size_t size, data::ByteData *dest, size_t destoffset)
{
return new GraphicsReadback(this, method, buffer, offset, size, dest, destoffset);
}
love::graphics::GraphicsReadback *Graphics::newReadbackInternal(ReadbackMethod method, love::graphics::Texture *texture, int slice, int mipmap, const Rect &rect, image::ImageData *dest, int destx, int desty)
{
return new GraphicsReadback(this, method, texture, slice, mipmap, rect, dest, destx, desty);
}
Matrix4 Graphics::computeDeviceProjection(const Matrix4 &projection, bool rendertotexture) const
{
uint32 flags = DEVICE_PROJECTION_DEFAULT;
@@ -462,14 +473,12 @@ void Graphics::unSetMode()
// mode change.
Volatile::unloadAll();
clearTemporaryResources();
for (const auto &pair : framebufferObjects)
gl.deleteFramebuffer(pair.second);
for (auto temp : temporaryTextures)
temp.texture->release();
framebufferObjects.clear();
temporaryTextures.clear();
if (mainVAO != 0)
{
@@ -1335,18 +1344,8 @@ void Graphics::present(void *screenshotCallbackData)
renderTargetSwitchCount = 0;
drawCallsBatched = 0;
// This assumes temporary textures will only be used within a render pass.
for (int i = (int) temporaryTextures.size() - 1; i >= 0; i--)
{
if (temporaryTextures[i].framesSinceUse >= MAX_TEMPORARY_TEXTURE_UNUSED_FRAMES)
{
temporaryTextures[i].texture->release();
temporaryTextures[i] = temporaryTextures.back();
temporaryTextures.pop_back();
}
else
temporaryTextures[i].framesSinceUse++;
}
updatePendingReadbacks();
updateTemporaryResources();
}
int Graphics::getRequestedBackbufferMSAA() const
+4
View File
@@ -141,6 +141,10 @@ private:
love::graphics::ShaderStage *newShaderStageInternal(ShaderStageType stage, const std::string &cachekey, const std::string &source, bool gles) override;
love::graphics::Shader *newShaderInternal(StrongRef<love::graphics::ShaderStage> stages[SHADERSTAGE_MAX_ENUM]) override;
love::graphics::StreamBuffer *newStreamBuffer(BufferUsage type, size_t size) override;
love::graphics::GraphicsReadback *newReadbackInternal(ReadbackMethod method, love::graphics::Buffer *buffer, size_t offset, size_t size, data::ByteData *dest, size_t destoffset) override;
love::graphics::GraphicsReadback *newReadbackInternal(ReadbackMethod method, love::graphics::Texture *texture, int slice, int mipmap, const Rect &rect, image::ImageData *dest, int destx, int desty) override;
void setRenderTargetsInternal(const RenderTargets &rts, int pixelw, int pixelh, bool hasSRGBtexture) override;
void initCapabilities() override;
void getAPIStats(int &shaderswitches) const override;
@@ -0,0 +1,126 @@
/**
* Copyright (c) 2006-2022 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
**/
#include "GraphicsReadback.h"
#include "Buffer.h"
#include "Texture.h"
#include "graphics/Graphics.h"
#include "data/ByteData.h"
namespace love
{
namespace graphics
{
namespace opengl
{
GraphicsReadback::GraphicsReadback(love::graphics::Graphics *gfx, ReadbackMethod method, love::graphics::Buffer *buffer, size_t offset, size_t size, data::ByteData *dest, size_t destoffset)
: love::graphics::GraphicsReadback(gfx, method, buffer, offset, size, dest, destoffset)
{
// Immediate readback of readback-type buffers doesn't need a staging buffer.
if (method != READBACK_IMMEDIATE || buffer->getDataUsage() != BUFFERDATAUSAGE_READBACK)
{
stagingBuffer = gfx->getTemporaryBuffer(size, DATAFORMAT_FLOAT, 0, BUFFERDATAUSAGE_READBACK);
gfx->copyBuffer(buffer, stagingBuffer, offset, 0, size);
}
if (method == READBACK_IMMEDIATE)
{
if (stagingBuffer.get())
{
status = readbackBuffer(stagingBuffer, 0, size);
gfx->releaseTemporaryBuffer(stagingBuffer);
}
else
{
status = readbackBuffer(buffer, offset, size);
}
}
else
{
sync.fence();
}
}
GraphicsReadback::GraphicsReadback(love::graphics::Graphics *gfx, ReadbackMethod method, love::graphics::Texture *texture, int slice, int mipmap, const Rect &rect, image::ImageData *dest, int destx, int desty)
: love::graphics::GraphicsReadback(gfx, method, texture, slice, mipmap, rect, dest, destx, desty)
{
size_t size = getPixelFormatSliceSize(textureFormat, rect.w, rect.h);
if (method == READBACK_IMMEDIATE)
{
void *dest = prepareReadbackDest(size);
love::thread::Lock lock(imageData->getMutex());
// Direct readback without copying avoids the need for a staging buffer,
// and lowers the system requirements of immediate RT readback.
Texture *t = (Texture *) texture;
t->readbackInternal(slice, mipmap, rect, imageData->getWidth(), size, dest);
status = STATUS_COMPLETE;
}
else
{
stagingBuffer = gfx->getTemporaryBuffer(size, DATAFORMAT_FLOAT, 0, BUFFERDATAUSAGE_READBACK);
gfx->copyTextureToBuffer(texture, stagingBuffer, slice, mipmap, rect, 0, 0);
sync.fence();
}
}
GraphicsReadback::~GraphicsReadback()
{
}
void GraphicsReadback::wait()
{
if (status != STATUS_WAITING)
return;
sync.cpuWait();
update();
}
void GraphicsReadback::update()
{
if (status != STATUS_WAITING)
return;
if (sync.isComplete())
{
if (stagingBuffer.get())
status = readbackBuffer(stagingBuffer, 0, stagingBuffer->getSize());
else
status = STATUS_ERROR;
if (stagingBuffer.get())
{
auto gfx = Module::getInstance<love::graphics::Graphics>(Module::M_GRAPHICS);
if (gfx != nullptr)
gfx->releaseTemporaryBuffer(stagingBuffer);
stagingBuffer.set(nullptr);
}
}
}
} // opengl
} // graphics
} // love
@@ -0,0 +1,56 @@
/**
* Copyright (c) 2006-2022 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
**/
#pragma once
// LOVE
#include "graphics/GraphicsReadback.h"
#include "FenceSync.h"
#include "common/math.h"
namespace love
{
namespace graphics
{
namespace opengl
{
class GraphicsReadback final : public love::graphics::GraphicsReadback
{
public:
GraphicsReadback(love::graphics::Graphics *gfx, ReadbackMethod method, love::graphics::Buffer *buffer, size_t offset, size_t size, data::ByteData *dest, size_t destoffset);
GraphicsReadback(love::graphics::Graphics *gfx, ReadbackMethod method, love::graphics::Texture *texture, int slice, int mipmap, const Rect &rect, image::ImageData *dest, int destx, int desty);
virtual ~GraphicsReadback();
void wait() override;
void update() override;
private:
FenceSync sync;
StrongRef<love::graphics::Buffer> stagingBuffer;
}; // GraphicsReadback
} // opengl
} // graphics
} // love
+1 -1
View File
@@ -843,7 +843,7 @@ GLenum OpenGL::getGLBufferDataUsage(BufferDataUsage usage)
case BUFFERDATAUSAGE_STREAM: return GL_STREAM_DRAW;
case BUFFERDATAUSAGE_DYNAMIC: return GL_DYNAMIC_DRAW;
case BUFFERDATAUSAGE_STATIC: return GL_STATIC_DRAW;
case BUFFERDATAUSAGE_STAGING:
case BUFFERDATAUSAGE_READBACK:
return (GLAD_VERSION_1_1 || GLAD_ES_VERSION_3_0) ? GL_STREAM_READ : GL_STREAM_DRAW;
default: return 0;
}
+35 -53
View File
@@ -506,30 +506,46 @@ void Texture::generateMipmapsInternal()
glGenerateMipmap(gltextype);
}
void Texture::readbackImageData(love::image::ImageData *data, int slice, int mipmap, const Rect &r)
void Texture::readbackInternal(int slice, int mipmap, const Rect &rect, int destwidth, size_t size, void *dest)
{
if (fbo == 0) // Should never be reached.
return;
// Not supported in GL with compressed textures...
if ((GLAD_VERSION_1_1 || GLAD_ES_VERSION_3_0) && !isCompressed())
glPixelStorei(GL_PACK_ROW_LENGTH, destwidth);
gl.bindTextureToUnit(this, 0, false);
bool isSRGB = false;
OpenGL::TextureFormat fmt = gl.convertPixelFormat(data->getFormat(), false, isSRGB);
OpenGL::TextureFormat fmt = gl.convertPixelFormat(format, false, isSRGB);
GLuint current_fbo = gl.getFramebuffer(OpenGL::FRAMEBUFFER_ALL);
gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, getFBO());
if (slice > 0 || mipmap > 0)
if (gl.isCopyTextureToBufferSupported())
{
int layer = texType == TEXTURE_CUBE ? 0 : slice;
int face = texType == TEXTURE_CUBE ? slice : 0;
gl.framebufferTexture(GL_COLOR_ATTACHMENT0, texType, texture, mipmap, layer, face);
if (isCompressed())
glGetCompressedTextureSubImage(texture, mipmap, rect.x, rect.y, slice, rect.w, rect.h, 1, size, dest);
else
glGetTextureSubImage(texture, mipmap, rect.x, rect.y, slice, rect.w, rect.h, 1, fmt.externalformat, fmt.type, size, dest);
}
else if (fbo)
{
GLuint current_fbo = gl.getFramebuffer(OpenGL::FRAMEBUFFER_ALL);
gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, getFBO());
if (slice > 0 || mipmap > 0)
{
int layer = texType == TEXTURE_CUBE ? 0 : slice;
int face = texType == TEXTURE_CUBE ? slice : 0;
gl.framebufferTexture(GL_COLOR_ATTACHMENT0, texType, texture, mipmap, layer, face);
}
glReadPixels(rect.x, rect.y, rect.w, rect.h, fmt.externalformat, fmt.type, dest);
if (slice > 0 || mipmap > 0)
gl.framebufferTexture(GL_COLOR_ATTACHMENT0, texType, texture, 0, 0, 0);
gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, current_fbo);
}
glReadPixels(r.x, r.y, r.w, r.h, fmt.externalformat, fmt.type, data->getData());
if (slice > 0 || mipmap > 0)
gl.framebufferTexture(GL_COLOR_ATTACHMENT0, texType, texture, 0, 0, 0);
gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, current_fbo);
if ((GLAD_VERSION_1_1 || GLAD_ES_VERSION_3_0) && !isCompressed())
glPixelStorei(GL_PACK_ROW_LENGTH, 0);
}
void Texture::copyFromBuffer(love::graphics::Buffer *source, size_t sourceoffset, int sourcewidth, size_t size, int slice, int mipmap, const Rect &rect)
@@ -558,46 +574,12 @@ void Texture::copyToBuffer(love::graphics::Buffer *dest, int slice, int mipmap,
GLuint glbuffer = (GLuint) dest->getHandle();
glBindBuffer(GL_PIXEL_PACK_BUFFER, glbuffer);
if (!isCompressed()) // Not supported in GL with compressed textures...
glPixelStorei(GL_PACK_ROW_LENGTH, destwidth);
gl.bindTextureToUnit(this, 0, false);
bool isSRGB = false;
OpenGL::TextureFormat fmt = gl.convertPixelFormat(format, false, isSRGB);
// glTexSubImage and friends copy from the active pixel_unpack_buffer by
// glTexSubImage and friends copy to the active PIXEL_PACK_BUFFER by
// treating the pointer as a byte offset.
uint8 *byteoffset = (uint8 *)(ptrdiff_t)destoffset;
if (gl.isCopyTextureToBufferSupported())
{
if (isCompressed())
glGetCompressedTextureSubImage(texture, mipmap, rect.x, rect.y, slice, rect.w, rect.h, 1, size, byteoffset);
else
glGetTextureSubImage(texture, mipmap, rect.x, rect.y, slice, rect.w, rect.h, 1, fmt.externalformat, fmt.type, size, byteoffset);
}
else if (fbo)
{
GLuint current_fbo = gl.getFramebuffer(OpenGL::FRAMEBUFFER_ALL);
gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, getFBO());
readbackInternal(slice, mipmap, rect, destwidth, size, byteoffset);
if (slice > 0 || mipmap > 0)
{
int layer = texType == TEXTURE_CUBE ? 0 : slice;
int face = texType == TEXTURE_CUBE ? slice : 0;
gl.framebufferTexture(GL_COLOR_ATTACHMENT0, texType, texture, mipmap, layer, face);
}
glReadPixels(rect.x, rect.y, rect.w, rect.h, fmt.externalformat, fmt.type, byteoffset);
if (slice > 0 || mipmap > 0)
gl.framebufferTexture(GL_COLOR_ATTACHMENT0, texType, texture, 0, 0, 0);
gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, current_fbo);
}
glPixelStorei(GL_PACK_ROW_LENGTH, 0);
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
}
+2 -2
View File
@@ -58,6 +58,8 @@ public:
inline GLuint getFBO() const { return fbo; }
void readbackInternal(int slice, int mipmap, const Rect &rect, int destwidth, size_t size, void *dest);
private:
void createTexture();
@@ -65,8 +67,6 @@ private:
void generateMipmapsInternal() override;
void readbackImageData(love::image::ImageData *imagedata, int slice, int mipmap, const Rect &rect) override;
Slices slices;
GLuint fbo;
+4 -4
View File
@@ -383,10 +383,10 @@ STRINGMAP_END(IndexDataType, INDEX_MAX_ENUM, indexType)
STRINGMAP_BEGIN(BufferDataUsage, BUFFERDATAUSAGE_MAX_ENUM, bufferDataUsage)
{
{ "stream", BUFFERDATAUSAGE_STREAM },
{ "dynamic", BUFFERDATAUSAGE_DYNAMIC },
{ "static", BUFFERDATAUSAGE_STATIC },
{ "staging", BUFFERDATAUSAGE_STAGING },
{ "stream", BUFFERDATAUSAGE_STREAM },
{ "dynamic", BUFFERDATAUSAGE_DYNAMIC },
{ "static", BUFFERDATAUSAGE_STATIC },
{ "readback", BUFFERDATAUSAGE_READBACK },
}
STRINGMAP_END(BufferDataUsage, BUFFERDATAUSAGE_MAX_ENUM, bufferDataUsage)
+1 -1
View File
@@ -111,7 +111,7 @@ enum BufferDataUsage
BUFFERDATAUSAGE_STREAM,
BUFFERDATAUSAGE_DYNAMIC,
BUFFERDATAUSAGE_STATIC,
BUFFERDATAUSAGE_STAGING,
BUFFERDATAUSAGE_READBACK,
BUFFERDATAUSAGE_MAX_ENUM
};
+126
View File
@@ -2141,6 +2141,126 @@ int w_newVideo(lua_State *L)
return 1;
}
int w_readbackBuffer(lua_State *L)
{
Buffer *b = luax_checkbuffer(L, 1);
lua_Integer offset = luaL_optinteger(L, 2, 0);
lua_Integer size = luaL_optinteger(L, 3, b->getSize() - offset);
data::ByteData *dest = nullptr;
size_t destoffset = 0;
if (!lua_isnoneornil(L, 4))
{
dest = luax_checktype<data::ByteData>(L, 4);
destoffset = (size_t) luaL_optinteger(L, 5, 0);
}
love::data::ByteData *data = nullptr;
luax_catchexcept(L, [&]() { data = instance()->readbackBuffer(b, offset, size, dest, destoffset); });
luax_pushtype(L, data);
data->release();
return 1;
}
int w_readbackBufferAsync(lua_State *L)
{
Buffer *b = luax_checkbuffer(L, 1);
lua_Integer offset = luaL_optinteger(L, 2, 0);
lua_Integer size = luaL_optinteger(L, 3, b->getSize() - offset);
data::ByteData *dest = nullptr;
size_t destoffset = 0;
if (!lua_isnoneornil(L, 4))
{
dest = luax_checktype<data::ByteData>(L, 4);
destoffset = (size_t) luaL_optinteger(L, 5, 0);
}
GraphicsReadback *r = nullptr;
luax_catchexcept(L, [&]() { r = instance()->readbackBufferAsync(b, offset, size, dest, destoffset); });
luax_pushtype(L, r);
r->release();
return 1;
}
int w_readbackTexture(lua_State *L)
{
Texture *t = luax_checktexture(L, 1);
int slice = 0;
if (t->getTextureType() != TEXTURE_2D)
slice = (int) luaL_checkinteger(L, 2) - 1;
int mipmap = (int) luaL_optinteger(L, 3, 1) - 1;
Rect rect = {0, 0, t->getPixelWidth(mipmap), t->getPixelHeight(mipmap)};
if (!lua_isnoneornil(L, 4))
{
rect.x = (int) luaL_checkinteger(L, 4);
rect.y = (int) luaL_checkinteger(L, 5);
rect.w = (int) luaL_checkinteger(L, 6);
rect.h = (int) luaL_checkinteger(L, 7);
}
image::ImageData *dest = nullptr;
int destx = 0;
int desty = 0;
if (!lua_isnoneornil(L, 8))
{
dest = luax_checktype<image::ImageData>(L, 8);
destx = (int) luaL_optinteger(L, 9, 0);
desty = (int) luaL_optinteger(L, 10, 0);
}
image::ImageData *imagedata = nullptr;
luax_catchexcept(L, [&]() { imagedata = instance()->readbackTexture(t, slice, mipmap, rect, dest, destx, desty); });
luax_pushtype(L, imagedata);
imagedata->release();
return 1;
}
int w_readbackTextureAsync(lua_State *L)
{
Texture *t = luax_checktexture(L, 1);
int slice = 0;
if (t->getTextureType() != TEXTURE_2D)
slice = (int) luaL_checkinteger(L, 2) - 1;
int mipmap = (int) luaL_optinteger(L, 3, 1) - 1;
Rect rect = {0, 0, t->getPixelWidth(mipmap), t->getPixelHeight(mipmap)};
if (!lua_isnoneornil(L, 4))
{
rect.x = (int) luaL_checkinteger(L, 4);
rect.y = (int) luaL_checkinteger(L, 5);
rect.w = (int) luaL_checkinteger(L, 6);
rect.h = (int) luaL_checkinteger(L, 7);
}
image::ImageData *dest = nullptr;
int destx = 0;
int desty = 0;
if (!lua_isnoneornil(L, 8))
{
dest = luax_checktype<image::ImageData>(L, 8);
destx = (int) luaL_optinteger(L, 9, 0);
desty = (int) luaL_optinteger(L, 10, 0);
}
GraphicsReadback *r = nullptr;
luax_catchexcept(L, [&]() { r = instance()->readbackTextureAsync(t, slice, mipmap, rect, dest, destx, desty); });
luax_pushtype(L, r);
r->release();
return 1;
}
int w_setColor(lua_State *L)
{
Colorf c;
@@ -3650,6 +3770,11 @@ static const luaL_Reg functions[] =
{ "newText", w_newText },
{ "_newVideo", w_newVideo },
{ "readbackBuffer", w_readbackBuffer },
{ "readbackBufferAsync", w_readbackBufferAsync },
{ "readbackTexture", w_readbackTexture },
{ "readbackTextureAsync", w_readbackTextureAsync },
{ "validateShader", w_validateShader },
{ "setCanvas", w_setCanvas },
@@ -3787,6 +3912,7 @@ static const lua_CFunction types[] =
luaopen_font,
luaopen_quad,
luaopen_graphicsbuffer,
luaopen_graphicsreadback,
luaopen_spritebatch,
luaopen_particlesystem,
luaopen_shader,
+1
View File
@@ -32,6 +32,7 @@
#include "wrap_Text.h"
#include "wrap_Video.h"
#include "wrap_Buffer.h"
#include "wrap_GraphicsReadback.h"
#include "Graphics.h"
namespace love
@@ -0,0 +1,95 @@
/**
* Copyright (c) 2006-2021 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
**/
// LOVE
#include "wrap_GraphicsReadback.h"
#include "data/ByteData.h"
#include "image/ImageData.h"
namespace love
{
namespace graphics
{
GraphicsReadback *luax_checkgraphicsreadback(lua_State *L, int idx)
{
return luax_checktype<GraphicsReadback>(L, idx);
}
int w_GraphicsReadback_isComplete(lua_State *L)
{
GraphicsReadback *t = luax_checkgraphicsreadback(L, 1);
luax_pushboolean(L, t->isComplete());
return 1;
}
int w_GraphicsReadback_hasError(lua_State *L)
{
GraphicsReadback *t = luax_checkgraphicsreadback(L, 1);
luax_pushboolean(L, t->hasError());
return 1;
}
int w_GraphicsReadback_wait(lua_State *L)
{
GraphicsReadback *t = luax_checkgraphicsreadback(L, 1);
t->wait();
return 0;
}
int w_GraphicsReadback_update(lua_State *L)
{
GraphicsReadback *t = luax_checkgraphicsreadback(L, 1);
luax_catchexcept(L, [&]() { t->update(); });
return 0;
}
int w_GraphicsReadback_getBufferData(lua_State *L)
{
GraphicsReadback *t = luax_checkgraphicsreadback(L, 1);
luax_pushtype(L, t->getBufferData());
return 1;
}
int w_GraphicsReadback_getImageData(lua_State *L)
{
GraphicsReadback *t = luax_checkgraphicsreadback(L, 1);
luax_pushtype(L, t->getImageData());
return 1;
}
static const luaL_Reg w_GraphicsReadback_functions[] =
{
{ "isComplete", w_GraphicsReadback_isComplete },
{ "hasError", w_GraphicsReadback_hasError },
{ "wait", w_GraphicsReadback_wait },
{ "update", w_GraphicsReadback_update },
{ "getBufferData", w_GraphicsReadback_getBufferData },
{ "getImageData", w_GraphicsReadback_getImageData },
{ 0, 0 }
};
extern "C" int luaopen_graphicsreadback(lua_State *L)
{
return luax_register_type(L, &GraphicsReadback::type, w_GraphicsReadback_functions, nullptr);
}
} // graphics
} // love
@@ -0,0 +1,36 @@
/**
* Copyright (c) 2006-2021 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
**/
#pragma once
// LOVE
#include "common/runtime.h"
#include "GraphicsReadback.h"
namespace love
{
namespace graphics
{
GraphicsReadback *luax_checkgraphicsreadback(lua_State *L, int idx);
extern "C" int luaopen_graphicsreadback(lua_State *L);
} // graphics
} // love
+12 -6
View File
@@ -385,16 +385,15 @@ int w_Texture_replacePixels(lua_State *L)
int w_Texture_newImageData(lua_State *L)
{
luax_markdeprecated(L, 1, "Texture:newImageData", API_METHOD, DEPRECATED_RENAMED, "love.graphics.readbackTexture");
Texture *t = luax_checktexture(L, 1);
love::image::Image *image = luax_getmodule<love::image::Image>(L, love::image::Image::type);
int slice = 0;
int mipmap = 0;
if (t->getTextureType() != TEXTURE_2D)
slice = (int) luaL_checkinteger(L, 2) - 1;
mipmap = (int) luaL_optinteger(L, 3, 1) - 1;
int mipmap = (int) luaL_optinteger(L, 3, 1) - 1;
Rect rect = {0, 0, t->getPixelWidth(mipmap), t->getPixelHeight(mipmap)};
if (!lua_isnoneornil(L, 4))
@@ -405,8 +404,12 @@ int w_Texture_newImageData(lua_State *L)
rect.h = (int) luaL_checkinteger(L, 7);
}
auto gfx = Module::getInstance<Graphics>(Module::M_GRAPHICS);
if (gfx == nullptr)
return luaL_error(L, "Cannot find Graphics module.");
love::image::ImageData *img = nullptr;
luax_catchexcept(L, [&](){ img = t->newImageData(image, slice, mipmap, rect); });
luax_catchexcept(L, [&](){ img = gfx->readbackTexture(t, slice, mipmap, rect, nullptr, 0, 0); });
luax_pushtype(L, img);
img->release();
@@ -501,8 +504,11 @@ const luaL_Reg w_Texture_functions[] =
{ "setDepthSampleMode", w_Texture_setDepthSampleMode },
{ "generateMipmaps", w_Texture_generateMipmaps },
{ "replacePixels", w_Texture_replacePixels },
{ "newImageData", w_Texture_newImageData },
{ "renderTo", w_Texture_renderTo },
// Deprecated
{ "newImageData", w_Texture_newImageData },
{ 0, 0 }
};
+2
View File
@@ -99,6 +99,8 @@ bool Keyboard::isModifierActive(ModifierKey key) const
return (modstate & KMOD_SCROLL) != 0;
case MODKEY_MODE:
return (modstate & KMOD_MODE) != 0;
default:
break;
}
return false;
+5
View File
@@ -129,6 +129,11 @@ function love.createhandlers()
localechanged = function ()
if love.localechanged then return love.localechanged() end
end,
audiodisconnected = function (sources)
if not love.audiodisconnected or not love.audiodisconnected(sources) then
love.audio.setPlaybackDevice()
end
end,
}, {
__index = function(self, name)
error("Unknown event: " .. name)