Implemented efficient FFI versions of ImageData methods.

Note that ImageData:mapPixel (with its optional sub-rectangle parameters) is often many times faster than a loop of ImageData:setPixel and/or getPixel, for anything more than a few pixels.
This commit is contained in:
Alex Szpakowski
2015-06-15 00:50:01 -03:00
parent 409d313bff
commit df1bd42dec
5 changed files with 289 additions and 43 deletions
+189
View File
@@ -0,0 +1,189 @@
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 ImageData_mt, ffifuncspointer = ...
local tonumber, assert = tonumber, assert
local type, pcall = type, pcall
local function inside(x, y, w, h)
return x >= 0 and x < w and y >= 0 and y < h
end
-- Implement thread-safe ImageData:mapPixel regardless of whether the FFI is
-- used or not.
function ImageData_mt.__index:mapPixel(func, ix, iy, iw, ih)
local idw, idh = self:getDimensions()
ix = ix or 0
iy = iy or 0
iw = iw or idw
ih = ih or idh
assert(type(ix) == "number", "Invalid argument #2 to ImageData:mapPixel (expected number)")
assert(type(iy) == "number", "Invalid argument #3 to ImageData:mapPixel (expected number)")
assert(type(iw) == "number", "Invalid argument #4 to ImageData:mapPixel (expected number)")
assert(type(ih) == "number", "Invalid argument #5 to ImageData:mapPixel (expected number)")
assert(type(func) == "function", "Invalid argument #1 to ImageData:mapPixel (expected function)")
assert(inside(ix, iy, idw, idh) and inside(ix+iw-1, iy+ih-1, idw, idh), "Invalid rectangle dimensions")
-- performAtomic and mapPixelUnsafe have Lua-C API and FFI versions.
self:_performAtomic(self._mapPixelUnsafe, self, func, ix, iy, iw, ih)
end
-- Everything below this point is efficient FFI replacements for existing
-- ImageData functionality.
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
pcall(ffi.cdef, [[
typedef struct Proxy Proxy;
typedef struct FFI_ImageData
{
void (*lockMutex)(Proxy *p);
void (*unlockMutex)(Proxy *p);
} FFI_ImageData;
typedef struct ImageData_Pixel
{
uint8_t r, g, b, a;
} ImageData_Pixel;
]])
local ffifuncs = ffi.cast("FFI_ImageData *", ffifuncspointer)
local pixelpointer = ffi.typeof("ImageData_Pixel *")
local _getWidth = ImageData_mt.__index.getWidth
local _getHeight = ImageData_mt.__index.getHeight
local _getDimensions = ImageData_mt.__index.getDimensions
-- Table which holds ImageData objects as keys, and information about the objects
-- as values. Uses weak keys so the ImageData objects can still be GC'd properly.
local objectcache = setmetatable({}, {
__mode = "k",
__index = function(self, imagedata)
local width, height = _getDimensions(imagedata)
local pointer = ffi.cast(pixelpointer, imagedata:getPointer())
local p = {
width = width,
height = height,
pointer = pointer,
}
self[imagedata] = p
return p
end,
})
-- Overwrite existing functions with new FFI versions.
function ImageData_mt.__index:_performAtomic(...)
ffifuncs.lockMutex(self)
local success, err = pcall(...)
ffifuncs.unlockMutex(self)
if not success then
error(err, 3)
end
end
function ImageData_mt.__index:_mapPixelUnsafe(func, ix, iy, iw, ih)
local p = objectcache[self]
local idw, idh = p.width, p.height
local pixels = p.pointer
for y=iy, iy+ih-1 do
for x=ix, ix+iw-1 do
local p = pixels[y*idw+x]
local r, g, b, a = func(x, y, tonumber(p.r), tonumber(p.g), tonumber(p.b), tonumber(p.a))
pixels[y*idw+x].r = r
pixels[y*idw+x].g = g
pixels[y*idw+x].b = b
pixels[y*idw+x].a = a == nil and 255 or a
end
end
end
function ImageData_mt.__index:getPixel(x, y)
local p = objectcache[self]
assert(inside(x, y, p.width, p.height), "Attempt to get out-of-range pixel!")
ffifuncs.lockMutex(self)
local pixel = p.pointer[y * p.width + x]
local r, g, b, a = tonumber(pixel.r), tonumber(pixel.g), tonumber(pixel.b), tonumber(pixel.a)
ffifuncs.unlockMutex(self)
return r, g, b, a
end
local temppixel = ffi.new("ImageData_Pixel")
function ImageData_mt.__index:setPixel(x, y, r, g, b, a)
local p = objectcache[self]
assert(inside(x, y, p.width, p.height), "Attempt to set out-of-range pixel!")
if type(r) == "table" then
local t = r
r, g, b, a = t[1], t[2], t[3], t[4]
end
temppixel.r = r
temppixel.g = g
temppixel.b = b
temppixel.a = a == nil and 255 or a
ffifuncs.lockMutex(self)
p.pointer[y * p.width + x] = temppixel
ffifuncs.unlockMutex(self)
end
function ImageData_mt.__index:getWidth()
return objectcache[self].width
end
function ImageData_mt.__index:getHeight()
return objectcache[self].height
end
function ImageData_mt.__index:getDimensions()
local p = objectcache[self]
return p.width, p.height
end
-- DO NOT REMOVE THE NEXT LINE. It is used to load this file as a C++ string.
--)luastring"--"