-- -- autobatch.lua -- -- Copyright (c) 2018 rxi -- -- This library is free software; you can redistribute it and/or modify it -- under the terms of the MIT license. See LICENSE for details. -- local autobatch = { _version = "0.1.1" } setmetatable(autobatch, { __index = love.graphics }) -- Use local reference to love.graphics as we overwrite it later local love_graphics = love.graphics autobatch.threshold = 4 autobatch.batches = setmetatable( {}, { __mode = "k" } ) autobatch.pending = { image = nil, draws = 0 } autobatch.color = { 255, 255, 255, 255 } autobatch.image = nil autobatch.stack = {} local function switchActiveImage(img) -- Draw and reset old image's spritebatch if we have one; if not we reset -- love's color state as the spritebatch's color state will be used instead if autobatch.image then local b = autobatch.batches[autobatch.image] love_graphics.draw(b.sb) b.sb:clear() b.count = 0 else love_graphics.setColor(255, 255, 255) end -- Activate spritebatch if image was not nil if img then local b = autobatch.batches[img] -- Create batch if it doesn't exist if not b then b = {} b.count = 0 b.capacity = 16 b.sb = love_graphics.newSpriteBatch(img, b.capacity, "stream") autobatch.batches[img] = b end -- Init spritebatch's color b.sb:setColor( unpack(autobatch.color) ) end -- Set new image autobatch.image = img end local function flushAndDraw(...) autobatch.flush() return love_graphics.draw(...) end function autobatch.draw(image, ...) if image ~= autobatch.image then if not autobatch.batches[image] then -- Only Textures (Image or Canvas) can be batched -- if the image is -- neither of these then we draw normally instead if not image:typeOf("Texture") then return flushAndDraw(image, ...) end -- Check to see if it's the pending image, and whether it's reached the -- draw count threshold; if so we can switch to it as the active image and -- create a batch for it, otherwise we just increment the count and draw -- it normally if image ~= autobatch.pending.image then autobatch.pending.image = image autobatch.pending.draws = 0 end autobatch.pending.draws = autobatch.pending.draws + 1 if autobatch.pending.draws < autobatch.threshold then return flushAndDraw(image, ...) end end -- Reset pending image autobatch.pending.image = nil -- Switch active image switchActiveImage(image) end -- Get active batch local b = autobatch.batches[autobatch.image] -- Increase spritebatch capacity if we've reached it if b.count == b.capacity then b.capacity = b.capacity * 2 b.sb:setBufferSize(b.capacity) end -- Add to spritebatch b.sb:add(...) b.count = b.count + 1 end function autobatch.setColor(r, g, b, a) local t = autobatch.color -- Unpack color if it's a table if type(r) == "table" then r, g, b, a = unpack(r) end -- Set default alpha a = a or 255 -- Exit early if color isn't different to the current color if t[1] == r and t[2] == g and t[3] == b and t[4] == a then return end -- Set active color t[1], t[2], t[3], t[4] = r, g, b, a -- Set active color on active spritebatch or in love.graphics if no -- image is active local b = autobatch.batches[autobatch.image] if b then b.sb:setColor( unpack(autobatch.color) ) else love_graphics.setColor( unpack(autobatch.color) ) end end function autobatch.getColor() return unpack(autobatch.color) end function autobatch.reset() autobatch.flush() autobatch.setColor(255, 255, 255, 255) love_graphics.reset() end function autobatch.push(stack) if stack == "all" then table.insert(autobatch.stack, { autobatch.getColor() }) else table.insert(autobatch.stack, false) end love_graphics.push(stack) end function autobatch.pop() autobatch.flush() love_graphics.pop() local color = table.remove(autobatch.stack) if color ~= false then autobatch.setColor(color) end end function autobatch.flush() -- If we have an active image set switch from it to draw the active batch and -- reset love's color to match our internal one if autobatch.image then switchActiveImage(nil) love_graphics.setColor( unpack(autobatch.color) ) end end local function wrap(fn) return function(...) autobatch.flush() return fn(...) end end -- Initialise wrapper functions for all graphics functions which change the -- state or draw to the screen, thus requiring that the autobatch be flushed -- prior. draw(), getColor(), pop(), push(), reset(), and setColor() are -- special cases handled above. local names = { "arc", "circle", "clear", "discard", "ellipse", "line", "points", "polygon", "present", "print", "printf", "rectangle", "stencil", "getStats", "setBlendMode", "setCanvas", "setColorMask", "setScissor", "setShader", "setStencilTest", "origin", "rotate", "scale", "shear", "translate", } for i, name in ipairs(names) do autobatch[name] = wrap( love_graphics[name] ) end -- Override love.graphics love.graphics = autobatch -- Wrap Canvas functions local mt = getmetatable( love_graphics.newCanvas(1, 1) ) local fn = mt.renderTo mt.renderTo = function(self, fn) local old = { autobatch.getCanvas() } autobatch.setCanvas(self) fn() autobatch.setCanvas( unpack(old) ) end mt.newImageData = wrap(mt.newImageData) -- Wrap Shader functions local mt = getmetatable( love_graphics.newShader([[ vec4 effect(vec4 a, Image b, vec2 c, vec2 d) { return a; } ]]) ) mt.send = wrap(mt.send) mt.sendColor = wrap(mt.sendColor) return autobatch