From 532c61c6c2170470124a3709153f60469fd39bed Mon Sep 17 00:00:00 2001 From: Alex Szpakowski Date: Sun, 14 Jun 2015 18:41:34 -0300 Subject: [PATCH] Implemented efficient FFI versions of SoundData methods, which are used when the LuaJIT FFI and JIT compiler are present. --- .../xcode/liblove.xcodeproj/project.pbxproj | 2 + src/common/runtime.cpp | 33 ++--- src/common/runtime.h | 5 +- src/modules/sound/wrap_SoundData.cpp | 27 +++- src/modules/sound/wrap_SoundData.lua | 116 ++++++++++++++++++ 5 files changed, 165 insertions(+), 18 deletions(-) create mode 100644 src/modules/sound/wrap_SoundData.lua diff --git a/platform/xcode/liblove.xcodeproj/project.pbxproj b/platform/xcode/liblove.xcodeproj/project.pbxproj index 9e3d337bd..98e029716 100644 --- a/platform/xcode/liblove.xcodeproj/project.pbxproj +++ b/platform/xcode/liblove.xcodeproj/project.pbxproj @@ -1513,6 +1513,7 @@ FAB17BF41ABFC4B100F9BA27 /* lz4hc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = lz4hc.h; sourceTree = ""; }; FAB2D5A81AABDD8A008224A4 /* TrueTypeRasterizer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TrueTypeRasterizer.cpp; sourceTree = ""; }; FAB2D5A91AABDD8A008224A4 /* TrueTypeRasterizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TrueTypeRasterizer.h; sourceTree = ""; }; + FAC734C11B2E021A00AB460A /* wrap_SoundData.lua */ = {isa = PBXFileReference; lastKnownFileType = text; path = wrap_SoundData.lua; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -2539,6 +2540,7 @@ FA0B7C971A95902C000E1D17 /* wrap_Sound.h */, FA0B7C981A95902C000E1D17 /* wrap_SoundData.cpp */, FA0B7C991A95902C000E1D17 /* wrap_SoundData.h */, + FAC734C11B2E021A00AB460A /* wrap_SoundData.lua */, ); path = sound; sourceTree = ""; diff --git a/src/common/runtime.cpp b/src/common/runtime.cpp index c983966a5..5d189e9c9 100644 --- a/src/common/runtime.cpp +++ b/src/common/runtime.cpp @@ -262,7 +262,7 @@ int luax_preload(lua_State *L, lua_CFunction f, const char *name) return 0; } -int luax_register_type(lua_State *L, love::Type type, const luaL_Reg *f) +int luax_register_type(lua_State *L, love::Type type, const luaL_Reg *f, bool pushmetatable) { // Verify that this type name has a matching Type ID and type name mapping. const char *tname = "Invalid"; @@ -270,9 +270,9 @@ int luax_register_type(lua_State *L, love::Type type, const luaL_Reg *f) printf("Missing type name entry for type ID %d\n", type); // Get the place for storing and re-using instantiated love types. - luax_getregistry(L, REGISTRY_TYPES); + luax_getregistry(L, REGISTRY_OBJECTS); - // Create registry._lovetypes if it doesn't exist yet. + // Create registry._loveobjects if it doesn't exist yet. if (!lua_istable(L, -1)) { lua_newtable(L); @@ -288,8 +288,8 @@ int luax_register_type(lua_State *L, love::Type type, const luaL_Reg *f) // setmetatable(newtable, metatable) lua_setmetatable(L, -2); - // registry._lovetypes = newtable - lua_setfield(L, LUA_REGISTRYINDEX, "_lovetypes"); + // registry._loveobjects = newtable + lua_setfield(L, LUA_REGISTRYINDEX, "_loveobjects"); } else lua_pop(L, 1); @@ -322,9 +322,12 @@ int luax_register_type(lua_State *L, love::Type type, const luaL_Reg *f) lua_pushcfunction(L, w__typeOf); lua_setfield(L, -2, "typeOf"); - if (f != 0) + if (f != nullptr) luax_setfuncs(L, f); + if (pushmetatable) + return 1; // leave the metatable on the stack. + lua_pop(L, 1); // Pops metatable. return 0; } @@ -406,8 +409,8 @@ void luax_pushtype(lua_State *L, love::Type type, love::Object *object) return; } - // Fetch the registry table of instantiated types. - luax_getregistry(L, REGISTRY_TYPES); + // Fetch the registry table of instantiated objects. + luax_getregistry(L, REGISTRY_OBJECTS); // The table might not exist - it should be insisted in luax_register_type. if (!lua_istable(L, -1)) @@ -416,7 +419,7 @@ void luax_pushtype(lua_State *L, love::Type type, love::Object *object) return luax_rawnewtype(L, type, object); } - // Get the value of lovetypes[object] on the stack. + // Get the value of loveobjects[object] on the stack. lua_pushlightuserdata(L, object); lua_gettable(L, -2); @@ -430,11 +433,11 @@ void luax_pushtype(lua_State *L, love::Type type, love::Object *object) lua_pushlightuserdata(L, object); lua_pushvalue(L, -2); - // lovetypes[object] = Proxy. + // loveobjects[object] = Proxy. lua_settable(L, -4); } - // Remove the lovetypes table from the stack. + // Remove the loveobjects table from the stack. lua_remove(L, -2); // Keep the Proxy userdata on the stack. @@ -586,8 +589,8 @@ int luax_insistregistry(lua_State *L, Registry r) return luax_insistlove(L, "_gc"); case REGISTRY_MODULES: return luax_insistlove(L, "_modules"); - case REGISTRY_TYPES: - return luax_insist(L, LUA_REGISTRYINDEX, "_lovetypes"); + case REGISTRY_OBJECTS: + return luax_insist(L, LUA_REGISTRYINDEX, "_loveobjects"); default: return luaL_error(L, "Attempted to use invalid registry."); } @@ -601,8 +604,8 @@ int luax_getregistry(lua_State *L, Registry r) return luax_getlove(L, "_gc"); case REGISTRY_MODULES: return luax_getlove(L, "_modules"); - case REGISTRY_TYPES: - lua_getfield(L, LUA_REGISTRYINDEX, "_lovetypes"); + case REGISTRY_OBJECTS: + lua_getfield(L, LUA_REGISTRYINDEX, "_loveobjects"); return 1; default: return luaL_error(L, "Attempted to use invalid registry."); diff --git a/src/common/runtime.h b/src/common/runtime.h index a4fb68586..55d831df9 100644 --- a/src/common/runtime.h +++ b/src/common/runtime.h @@ -51,7 +51,7 @@ enum Registry { REGISTRY_GC, REGISTRY_MODULES, - REGISTRY_TYPES + REGISTRY_OBJECTS }; /** @@ -249,8 +249,9 @@ int luax_preload(lua_State *L, lua_CFunction f, const char *name); * Register a new type. * @param type The type. * @param f The list of member functions for the type. + * @param pushmetatable Whether to push the type's metatable to the stack. **/ -int luax_register_type(lua_State *L, love::Type type, const luaL_Reg *f = 0); +int luax_register_type(lua_State *L, love::Type type, const luaL_Reg *f = nullptr, bool pushmetatable = false); /** * Do a table.insert from C diff --git a/src/modules/sound/wrap_SoundData.cpp b/src/modules/sound/wrap_SoundData.cpp index 2f76ada5c..b4fc7b7ad 100644 --- a/src/modules/sound/wrap_SoundData.cpp +++ b/src/modules/sound/wrap_SoundData.cpp @@ -22,11 +22,21 @@ #include "common/wrap_Data.h" +// Shove the wrap_SoundData.lua code directly into a raw string literal. +static const char sounddata_lua[] = +#include "wrap_SoundData.lua" +; + namespace love { namespace sound { +/** + * NOTE: Additional wrapper code is in wrap_SoundData.lua. Be sure to keep it + * in sync with any changes made to this file! + **/ + SoundData *luax_checksounddata(lua_State *L, int idx) { return luax_checktype(L, idx, SOUND_SOUND_DATA_ID); @@ -105,7 +115,22 @@ static const luaL_Reg functions[] = extern "C" int luaopen_sounddata(lua_State *L) { - return luax_register_type(L, SOUND_SOUND_DATA_ID, functions); + // The last argument pushes the type's metatable onto the stack. + int ret = luax_register_type(L, SOUND_SOUND_DATA_ID, functions, true); + + // Load and execute SoundData.lua, sending the metatable as an argument. + if (ret > 0) + { + luaL_loadbuffer(L, sounddata_lua, sizeof(sounddata_lua), "SoundData.lua"); + lua_pushvalue(L, -2); + lua_call(L, 1, 0); + + // Pop the metatable. + lua_pop(L, 1); + ret--; + } + + return ret; } } // sound diff --git a/src/modules/sound/wrap_SoundData.lua b/src/modules/sound/wrap_SoundData.lua new file mode 100644 index 000000000..8798f1524 --- /dev/null +++ b/src/modules/sound/wrap_SoundData.lua @@ -0,0 +1,116 @@ +R"luastring"--( +-- DO NOT REMOVE THE ABOVE LINE. It is used to load this file as a C++ string. +-- There is a matching delimiter at the bottom of the file. + +--[[ +Copyright (c) 2006-2015 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. +--]] + +local SoundData_mt = ... + +if type(jit) ~= "table" or not jit.status() then + -- LuaJIT's FFI is *much* slower than LOVE's regular methods when the JIT + -- compiler is disabled. + return +end + +local status, ffi = pcall(require, "ffi") +if not status then return end + +local tonumber, assert = tonumber, assert + +local float = ffi.typeof("float") +local datatypes = {ffi.typeof("uint8_t *"), ffi.typeof("int16_t *")} + +local typemaxvals = {0x7F, 0x7FFF} + +local _getBitDepth = SoundData_mt.__index.getBitDepth +local _getSampleCount = SoundData_mt.__index.getSampleCount +local _getSampleRate = SoundData_mt.__index.getSampleRate +local _getChannels = SoundData_mt.__index.getChannels + +-- Table which holds SoundData objects as keys, and information about the objects +-- as values. Uses weak keys so the SoundData objects can still be GC'd properly. +local objectcache = setmetatable({}, { + __mode = "k", + __index = function(self, sounddata) + local bytedepth = _getBitDepth(sounddata) / 8 + local pointer = ffi.cast(datatypes[bytedepth], sounddata:getPointer()) + + local p = { + bytedepth = bytedepth, + pointer = pointer, + size = sounddata:getSize(), + maxvalue = typemaxvals[bytedepth], + samplecount = _getSampleCount(sounddata), + samplerate = _getSampleRate(sounddata), + channels = _getChannels(sounddata), + } + + self[sounddata] = p + return p + end, +}) + + +-- Overwrite existing functions with new FFI versions. + +function SoundData_mt.__index:getSample(i) + local p = objectcache[self] + assert(i >= 0 and i < p.size/p.bytedepth, "Attempt to get out-of-range sample!") + if p.bytedepth == 2 then + -- 16-bit data is stored as signed values internally. + return tonumber(p.pointer[i]) / p.maxvalue + else + -- 8-bit data is stored as unsigned values internally. + return (tonumber(p.pointer[i]) - 128) / 127 + end +end + +function SoundData_mt.__index:setSample(i, sample) + local p = objectcache[self] + assert(i >= 0 and i < p.size/p.bytedepth, "Attempt to set out-of-range sample!") + if p.bytedepth == 2 then + -- 16-bit data is stored as signed values internally. + p.pointer[i] = sample * p.maxvalue + else + -- 8-bit data is stored as unsigned values internally. + -- The float cast is needed to make values end up the same as in the + -- C++ version of this method. + p.pointer[i] = ffi.cast(float, (sample * 127) + 128) + end +end + +function SoundData_mt.__index:getSampleCount() + local p = objectcache[self] + return p.samplecount +end + +function SoundData_mt.__index:getSampleRate() + local p = objectcache[self] + return p.samplerate +end + +function SoundData_mt.__index:getChannels() + local p = objectcache[self] + return p.channels +end + +-- DO NOT REMOVE THE NEXT LINE. It is used to load this file as a C++ string. +--)luastring"--"