├── examples ├── low-res │ ├── mario.png │ ├── background.png │ └── init.lua ├── single-shader │ ├── love.png │ ├── shader.fs │ └── init.lua ├── stencil │ ├── background.png │ └── init.lua ├── canvases-shaders │ ├── love1.png │ ├── love2.png │ ├── shader1.fs │ ├── shader2.fs │ └── init.lua ├── multiple-shaders │ ├── love.png │ ├── shader1.fs │ ├── shader2.fs │ └── init.lua └── mouse-input │ └── init.lua ├── main.lua ├── LICENSE ├── README.md └── push.lua /examples/low-res/mario.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ulydev/push/HEAD/examples/low-res/mario.png -------------------------------------------------------------------------------- /examples/low-res/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ulydev/push/HEAD/examples/low-res/background.png -------------------------------------------------------------------------------- /examples/single-shader/love.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ulydev/push/HEAD/examples/single-shader/love.png -------------------------------------------------------------------------------- /examples/stencil/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ulydev/push/HEAD/examples/stencil/background.png -------------------------------------------------------------------------------- /examples/canvases-shaders/love1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ulydev/push/HEAD/examples/canvases-shaders/love1.png -------------------------------------------------------------------------------- /examples/canvases-shaders/love2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ulydev/push/HEAD/examples/canvases-shaders/love2.png -------------------------------------------------------------------------------- /examples/multiple-shaders/love.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ulydev/push/HEAD/examples/multiple-shaders/love.png -------------------------------------------------------------------------------- /examples/single-shader/shader.fs: -------------------------------------------------------------------------------- 1 | //from "Share a Shader" forum thread 2 | 3 | extern float strength = 2.0f; 4 | 5 | vec4 effect(vec4 color, Image tex, vec2 tC, vec2 pC){ 6 | vec2 dir = .5 - tC; 7 | float dist = sqrt(dir.x * dir.x + dir.y * dir.y); 8 | 9 | vec4 c = Texel(tex, tC); 10 | 11 | float t = dist * strength; 12 | t = clamp(t, 0, 1); 13 | 14 | return mix(c, vec4(0), t); 15 | } -------------------------------------------------------------------------------- /examples/canvases-shaders/shader1.fs: -------------------------------------------------------------------------------- 1 | //from "Share a Shader" forum thread 2 | 3 | extern number shift = 0; 4 | vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 pixel_coords) 5 | { 6 | vec2 tc = texture_coords; 7 | vec2 scale = vec2(1.0/800.0, 1.0/600.0); 8 | vec4 r = Texel(texture, vec2(tc.x + shift * scale.x, tc.y - shift * scale.y)); 9 | vec4 g = Texel(texture, vec2(tc.x, tc.y + shift*scale.y)); 10 | vec4 b = Texel(texture, vec2(tc.x - shift*scale.x, tc.y - shift*scale.y)); 11 | number a = r.a+g.a+b.a / 3.0; 12 | 13 | return vec4(r.r, g.g, b.b, a); 14 | } -------------------------------------------------------------------------------- /examples/multiple-shaders/shader1.fs: -------------------------------------------------------------------------------- 1 | //from "Share a Shader" forum thread 2 | 3 | extern number shift = 0; 4 | vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 pixel_coords) 5 | { 6 | vec2 tc = texture_coords; 7 | vec2 scale = vec2(1.0/800.0, 1.0/600.0); 8 | vec4 r = Texel(texture, vec2(tc.x + shift * scale.x, tc.y - shift * scale.y)); 9 | vec4 g = Texel(texture, vec2(tc.x, tc.y + shift*scale.y)); 10 | vec4 b = Texel(texture, vec2(tc.x - shift*scale.x, tc.y - shift*scale.y)); 11 | number a = r.a+g.a+b.a / 3.0; 12 | 13 | return vec4(r.r, g.g, b.b, a); 14 | } -------------------------------------------------------------------------------- /examples/canvases-shaders/shader2.fs: -------------------------------------------------------------------------------- 1 | extern number time; 2 | extern float setting1 = 50.f; 3 | extern float setting2 = 300.f; 4 | vec4 effect( vec4 color, Image tex, vec2 tex_uv, vec2 pix_uv ) 5 | { 6 | // per row offset 7 | float f = sin( tex_uv.y * setting1 * 3.14f ); 8 | // scale to per pixel 9 | float o = f * (0.35f / setting2); 10 | // scale for subtle effect 11 | float s = f * .07f + 0.97f; 12 | // scan line fading 13 | float l = sin( time * 32.f )*.03f + 0.97f; 14 | // sample in 3 colour offset 15 | float r = Texel( tex, vec2( tex_uv.x+o, tex_uv.y+o ) ).x; 16 | float g = Texel( tex, vec2( tex_uv.x-o, tex_uv.y+o ) ).y; 17 | float b = Texel( tex, vec2( tex_uv.x , tex_uv.y-o ) ).z; 18 | // combine as 19 | return vec4( r*0.95f, g, b*0.95f, l ) * s; 20 | } -------------------------------------------------------------------------------- /examples/multiple-shaders/shader2.fs: -------------------------------------------------------------------------------- 1 | extern number time; 2 | extern float setting1 = 50.f; 3 | extern float setting2 = 300.f; 4 | vec4 effect( vec4 color, Image tex, vec2 tex_uv, vec2 pix_uv ) 5 | { 6 | // per row offset 7 | float f = sin( tex_uv.y * setting1 * 3.14f ); 8 | // scale to per pixel 9 | float o = f * (0.35f / setting2); 10 | // scale for subtle effect 11 | float s = f * .07f + 0.97f; 12 | // scan line fading 13 | float l = sin( time * 32.f )*.03f + 0.97f; 14 | // sample in 3 colour offset 15 | float r = Texel( tex, vec2( tex_uv.x+o, tex_uv.y+o ) ).x; 16 | float g = Texel( tex, vec2( tex_uv.x-o, tex_uv.y+o ) ).y; 17 | float b = Texel( tex, vec2( tex_uv.x , tex_uv.y-o ) ).z; 18 | // combine as 19 | return vec4( r*0.95f, g, b*0.95f, l ) * s; 20 | } -------------------------------------------------------------------------------- /main.lua: -------------------------------------------------------------------------------- 1 | io.stdout:setvbuf('no') 2 | 3 | push = require "push" --require the library 4 | love.window.setTitle("Press space to switch examples") 5 | 6 | local examples = { 7 | "low-res", 8 | "single-shader", 9 | "multiple-shaders", 10 | "mouse-input", 11 | "canvases-shaders", 12 | "stencil" 13 | } 14 | local example = 1 15 | 16 | for i = 1, #examples do 17 | examples[i] = require("examples." .. examples[i]) 18 | end 19 | 20 | function love.resize(w, h) 21 | push:resize(w, h) 22 | end 23 | 24 | function love.keypressed(key, scancode, isrepeat) 25 | 26 | if key == "space" then 27 | example = (example < #examples) and example + 1 or 1 28 | 29 | --be sure to reset push settings 30 | push:resetSettings() 31 | 32 | examples[example]() 33 | love.load() 34 | elseif key == "f" then --activate fullscreen mode 35 | push:switchFullscreen() --optional width and height parameters for window mode 36 | end 37 | 38 | end 39 | 40 | examples[example]() -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Ulysse Ramage 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/single-shader/init.lua: -------------------------------------------------------------------------------- 1 | --[[ Shader usage ]]-- 2 | 3 | return function () 4 | 5 | love.graphics.setDefaultFilter("linear", "linear") --default filter 6 | 7 | local gameWidth, gameHeight = 1080, 720 8 | 9 | local windowWidth, windowHeight = love.window.getDesktopDimensions() 10 | windowWidth, windowHeight = windowWidth*.5, windowHeight*.5 11 | 12 | push:setupScreen(gameWidth, gameHeight, windowWidth, windowHeight, { 13 | fullscreen = false, 14 | resizable = true, 15 | highdpi = true, 16 | canvas = true 17 | }) 18 | 19 | time = 0 20 | 21 | function love.load() 22 | image = love.graphics.newImage( "examples/single-shader/love.png" ) 23 | 24 | shader = love.graphics.newShader("examples/single-shader/shader.fs") 25 | push:setShader( shader ) 26 | end 27 | 28 | function love.update(dt) 29 | time = (time + dt) % 1 30 | shader:send("strength", 2 + math.cos(time * math.pi * 2) * .4) 31 | end 32 | 33 | function love.draw() 34 | push:apply("start") 35 | 36 | love.graphics.setColor(255, 255, 255) 37 | love.graphics.draw(image, (gameWidth-image:getWidth())*.5, (gameHeight-image:getHeight())*.5) 38 | 39 | push:apply("end") 40 | end 41 | 42 | end -------------------------------------------------------------------------------- /examples/multiple-shaders/init.lua: -------------------------------------------------------------------------------- 1 | --[[ Multiple shaders usage ]]-- 2 | 3 | return function () 4 | 5 | love.graphics.setDefaultFilter("linear", "linear") --default filter 6 | 7 | local gameWidth, gameHeight = 1080, 720 8 | 9 | local windowWidth, windowHeight = love.window.getDesktopDimensions() 10 | windowWidth, windowHeight = windowWidth*.5, windowHeight*.5 11 | 12 | push:setupScreen(gameWidth, gameHeight, windowWidth, windowHeight, { 13 | fullscreen = false, 14 | resizable = true, 15 | highdpi = true, 16 | canvas = true 17 | }) 18 | 19 | time = 0 20 | 21 | function love.load() 22 | image = love.graphics.newImage( "examples/multiple-shaders/love.png" ) 23 | 24 | shader1 = love.graphics.newShader("examples/multiple-shaders/shader1.fs") 25 | shader2 = love.graphics.newShader("examples/multiple-shaders/shader2.fs") 26 | push:setShader({ shader1, shader2 }) 27 | end 28 | 29 | function love.update(dt) 30 | time = (time + dt) % 1 31 | 32 | shader1:send("shift", 4 + math.cos( time * math.pi * 2 ) * 2) 33 | shader2:send("setting1", 40 + math.cos(love.timer.getTime() * 2) * 10) 34 | end 35 | 36 | function love.draw() 37 | push:apply("start") 38 | 39 | love.graphics.setColor(255, 255, 255) 40 | love.graphics.draw(image, (gameWidth-image:getWidth())*.5, (gameHeight-image:getHeight())*.5) 41 | 42 | push:apply("end") 43 | end 44 | 45 | end -------------------------------------------------------------------------------- /examples/mouse-input/init.lua: -------------------------------------------------------------------------------- 1 | --[[ Mouse input ]]-- 2 | 3 | return function () 4 | 5 | love.graphics.setDefaultFilter("linear", "linear") --default filter 6 | 7 | local gameWidth, gameHeight = 1080, 720 8 | 9 | local windowWidth, windowHeight = love.window.getDesktopDimensions() 10 | windowWidth, windowHeight = windowWidth*.5, windowHeight*.5 11 | 12 | push:setupScreen(gameWidth, gameHeight, windowWidth, windowHeight, { 13 | fullscreen = false, 14 | resizable = true, 15 | highdpi = true, 16 | canvas = false 17 | }) 18 | push:setBorderColor(0, 0, 0) --default value 19 | 20 | function love.load() 21 | love.graphics.setNewFont(32) 22 | end 23 | 24 | function love.draw() 25 | push:apply("start") 26 | 27 | love.graphics.setColor(50, 0, 0) 28 | love.graphics.rectangle("fill", 0, 0, gameWidth, gameHeight) 29 | 30 | local mouseX, mouseY = love.mouse.getPosition() 31 | mouseX, mouseY = push:toGame(mouseX, mouseY) 32 | --nil is returned if mouse is outside the game screen 33 | 34 | love.graphics.setColor(255, 255, 255) 35 | if mouseX and mouseY then love.graphics.circle("line", mouseX, mouseY, 10) end 36 | 37 | love.graphics.printf("mouse x : " .. (mouseX or "outside"), 25, 25, gameWidth, "left") 38 | love.graphics.printf("mouse y : " .. (mouseY or "outside"), 25, 50, gameWidth, "left") 39 | 40 | push:apply("end") 41 | end 42 | 43 | end -------------------------------------------------------------------------------- /examples/canvases-shaders/init.lua: -------------------------------------------------------------------------------- 1 | --[[ Multiple canvases and shaders ]]-- 2 | 3 | return function () 4 | 5 | love.graphics.setDefaultFilter("linear", "linear") --default filter 6 | 7 | local gameWidth, gameHeight = 800, 600 8 | 9 | local windowWidth, windowHeight = love.window.getDesktopDimensions() 10 | windowWidth, windowHeight = windowWidth*.5, windowHeight*.5 11 | 12 | push:setupScreen(gameWidth, gameHeight, windowWidth, windowHeight, { 13 | fullscreen = false, 14 | resizable = true, 15 | highdpi = true, 16 | canvas = true 17 | }) 18 | 19 | time = 0 20 | 21 | function love.load() 22 | image1 = love.graphics.newImage( "examples/canvases-shaders/love1.png" ) 23 | image2 = love.graphics.newImage( "examples/canvases-shaders/love2.png" ) 24 | 25 | shader1 = love.graphics.newShader("examples/canvases-shaders/shader1.fs") 26 | shader2 = love.graphics.newShader("examples/canvases-shaders/shader2.fs") 27 | 28 | push:setupCanvas({ 29 | { name = "shader", shader = shader1 }, --applied only to one canvas 30 | { name = "noshader" } 31 | }) 32 | push:setShader( shader2 ) --applied to final render 33 | end 34 | 35 | function love.update(dt) 36 | time = (time + dt) % 1 37 | 38 | shader1:send("shift", 4 + math.cos( time * math.pi * 2 ) * .5) 39 | shader2:send("time", love.timer.getTime()) 40 | end 41 | 42 | function love.draw() 43 | push:apply("start") 44 | 45 | love.graphics.setColor(255, 255, 255) 46 | 47 | push:setCanvas("shader") 48 | love.graphics.draw(image1, (gameWidth-image1:getWidth())*.5, (gameHeight-image1:getHeight())*.5 - 100) --global shader + canvas shader will be applied 49 | 50 | push:setCanvas("noshader") 51 | love.graphics.draw(image2, (gameWidth-image2:getWidth())*.5, (gameHeight-image2:getHeight())*.5 + 100) --only global shader will be applied 52 | 53 | push:apply("end") 54 | end 55 | 56 | end -------------------------------------------------------------------------------- /examples/stencil/init.lua: -------------------------------------------------------------------------------- 1 | --[[ Stencil ]]-- 2 | 3 | return function() 4 | 5 | love.graphics.setDefaultFilter("nearest", "nearest") --disable blurry scaling 6 | 7 | local gameWidth, gameHeight = 64, 64 8 | 9 | local windowWidth, windowHeight = love.window.getDesktopDimensions() 10 | windowWidth, windowHeight = windowWidth*.5, windowHeight*.5 11 | 12 | push:setupScreen(gameWidth, gameHeight, windowWidth, windowHeight, { 13 | fullscreen = false, 14 | resizable = true, 15 | pixelperfect = true 16 | }) 17 | push:setBorderColor{0, 0, 0} --default value 18 | 19 | push:setupCanvas({ 20 | { name = 'main_canvas' }, 21 | { name = 'stencil_canvas', stencil = true} 22 | }) 23 | 24 | -- 25 | 26 | time = 0 27 | 28 | function love.load() 29 | mario = love.graphics.newImage("examples/low-res/mario.png") 30 | background = love.graphics.newImage("examples/low-res/background.png") 31 | 32 | love.graphics.setNewFont(32) 33 | end 34 | 35 | function love.update(dt) 36 | 37 | time = (time + dt) % 1 38 | 39 | end 40 | 41 | function love.draw() 42 | push:apply("start") 43 | 44 | -- apply stencil 45 | push:setCanvas("stencil_canvas") 46 | love.graphics.stencil(function() 47 | love.graphics.setColor(1, 1, 1) 48 | local time = love.timer.getTime() * 3 49 | love.graphics.circle("fill", push:getWidth()*.5 + math.cos(time) * 20, push:getHeight()*.5 + math.sin(time) * 20, 10 + math.sin(time) * 2) 50 | end, 'replace', 1) 51 | 52 | -- draw background with stencil 53 | love.graphics.setStencilTest('greater', 0) 54 | love.graphics.draw(background, 0, 0) 55 | love.graphics.setStencilTest() 56 | 57 | -- switch to main canvas unaffected by stencil, but drawn behind stencil_canvas 58 | -- (this is why the circle draws on top of the mouse) 59 | push:setCanvas("main_canvas") 60 | 61 | love.graphics.setColor(1, 1, 1) 62 | local mouseX, mouseY = love.mouse.getPosition() 63 | mouseX, mouseY = push:toGame(mouseX, mouseY) 64 | --if nil is returned, that means the mouse is outside the game screen 65 | if mouseX and mouseY then --cursor 66 | love.graphics.points( 67 | mouseX, mouseY-1, 68 | mouseX-1, mouseY, 69 | mouseX, mouseY, 70 | mouseX+1, mouseY, 71 | mouseX, mouseY+1 72 | ) 73 | end 74 | 75 | push:apply("end") 76 | end 77 | 78 | end -------------------------------------------------------------------------------- /examples/low-res/init.lua: -------------------------------------------------------------------------------- 1 | --[[ Low resolution ]]-- 2 | 3 | return function() 4 | 5 | love.graphics.setDefaultFilter("nearest", "nearest") --disable blurry scaling 6 | 7 | local gameWidth, gameHeight = 64, 64 8 | 9 | local windowWidth, windowHeight = love.window.getDesktopDimensions() 10 | windowWidth, windowHeight = windowWidth*.5, windowHeight*.5 11 | 12 | push:setupScreen(gameWidth, gameHeight, windowWidth, windowHeight, { 13 | fullscreen = false, 14 | resizable = true, 15 | pixelperfect = true 16 | }) 17 | push:setBorderColor{0, 0, 0} --default value 18 | 19 | -- 20 | 21 | time = 0 22 | 23 | function love.load() 24 | mario = love.graphics.newImage("examples/low-res/mario.png") 25 | background = love.graphics.newImage("examples/low-res/background.png") 26 | 27 | love.graphics.setNewFont(16) 28 | end 29 | 30 | function love.update(dt) 31 | 32 | time = (time + dt) % 1 33 | 34 | end 35 | 36 | function love.draw() 37 | push:apply("start") 38 | 39 | local mouseX, mouseY = love.mouse.getPosition() 40 | mouseX, mouseY = push:toGame(mouseX, mouseY) 41 | --if nil is returned, that means the mouse is outside the game screen 42 | 43 | local abs = math.abs(time-.5) 44 | local pi = math.cos(math.pi*2*time) 45 | local pi2 = math.cos(math.pi*8*time) 46 | local w = push:getWidth() 47 | --for animating basic stuff 48 | 49 | love.graphics.draw(background, 0, 0) 50 | 51 | love.graphics.setScissor(0, 0, push:getWidth(), push:getHeight()-16) 52 | love.graphics.setColor(0, 0, 0, 100) 53 | love.graphics.draw(mario, 27, 33) 54 | love.graphics.setScissor() 55 | love.graphics.setColor(255, 255, 255) 56 | love.graphics.draw(mario, 26, 32) 57 | 58 | love.graphics.setColor(0, 0, 0, 100) 59 | love.graphics.printf("Hi!", 34+1, 22-pi*2+1, w, "center", -.15+.5*abs, abs*.25+1, abs*.25+1, w*.5, 12) 60 | love.graphics.setColor(255, 255, 255) 61 | love.graphics.printf("Hi!", 34, 22-pi*2, w, "center", -.15+.5*abs, abs*.25+1, abs*.25+1, w*.5, 12) 62 | 63 | love.graphics.setColor(255, 255, 255) 64 | if mouseX and mouseY then --cursor 65 | love.graphics.points( 66 | mouseX, mouseY-1, 67 | mouseX-1, mouseY, 68 | mouseX, mouseY, 69 | mouseX+1, mouseY, 70 | mouseX, mouseY+1 71 | ) 72 | end 73 | 74 | push:apply("end") 75 | end 76 | 77 | end -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **NOTE:** Check out the `dev` branch for some of the latest features in development. 2 | 3 | push 4 | ============== 5 | 6 | push is a simple resolution-handling library that allows you to focus on making your game with a fixed resolution. 7 | 8 | ![image](https://media.giphy.com/media/xTb1RycLHeAOPDownu/giphy.gif) 9 | 10 | Setup 11 | ---------------- 12 | Fullscreen 13 | ```lua 14 | local push = require "push" 15 | 16 | local gameWidth, gameHeight = 1080, 720 --fixed game resolution 17 | local windowWidth, windowHeight = love.window.getDesktopDimensions() 18 | 19 | push:setupScreen(gameWidth, gameHeight, windowWidth, windowHeight, {fullscreen = true}) 20 | 21 | function love.draw() 22 | push:start() 23 | 24 | --draw here 25 | 26 | push:finish() 27 | end 28 | ``` 29 | 30 | Windowed 31 | ```lua 32 | local push = require "push" 33 | 34 | local gameWidth, gameHeight = 1080, 720 --fixed game resolution 35 | local windowWidth, windowHeight = love.window.getDesktopDimensions() 36 | windowWidth, windowHeight = windowWidth*.7, windowHeight*.7 --make the window a bit smaller than the screen itself 37 | 38 | push:setupScreen(gameWidth, gameHeight, windowWidth, windowHeight, {fullscreen = false}) 39 | 40 | function love.draw() 41 | push:start() 42 | 43 | --draw here 44 | 45 | push:finish() 46 | end 47 | ``` 48 | 49 | Usage 50 | ---------------- 51 | 52 | Init push 53 | ```lua 54 | push:setupScreen(gameWidth, gameHeight, windowWidth, windowHeight, {fullscreen, resizable, canvas, pixelperfect}) 55 | ``` 56 | **gameWidth**, **gameHeight** represent the game's fixed resolution. **windowWidth** and **windowHeight** are the dimensions of the window you need to adapt the game to. 57 | 58 | The last argument is a table containing: 59 | - **fullscreen** (bool): turns fullscreen mode on or off 60 | - **resizable** (bool): allows resizing the window 61 | - **canvas** (bool): uses canvas 62 | - **pixelperfect** (bool): enables pixel-perfect mode (integer scaling 1x, 2x, 3x, ...) 63 | - **highdpi** (bool): enables high-dpi mode on supported screens (e.g. Retina) 64 | - **stretched** (bool): stretches the game to window dimensions 65 | 66 | Apply **push** transforms 67 | ```lua 68 | push:start() 69 | --draw here 70 | push:finish() 71 | 72 | --alias 73 | push:apply(operation) 74 | ``` 75 | **operation** should be equal to "start" or "end", meaning "before" or "after" your main drawing logic 76 | 77 | Mobile support 78 | ---------------- 79 | 80 | **push** does *not* have built-in support for mobile platforms, but it is trivial to handle mobile screens correctly. 81 | 82 | A possible solution is to initialize **push** in fullscreen mode: 83 | ```lua 84 | local screenWidth, screenHeight = love.window.getDesktopDimensions() 85 | push:setupScreen(gameWidth, gameHeight, screenWidth, screenHeight, { fullscreen = true, resizable = false, ... }) 86 | ``` 87 | 88 | And listen to screen orientation changes: 89 | ```lua 90 | function love.resize(w, h) 91 | return push:resize(w, h) 92 | end 93 | ``` 94 | 95 | Multiple shaders 96 | ---------------- 97 | 98 | Any method that takes a shader as an argument can also take a *table* of shaders instead. The shaders will be applied in the order they're provided. 99 | 100 | Set multiple global shaders 101 | ```lua 102 | push:setShader({ shader1, shader2 }) 103 | ``` 104 | 105 | Set multiple canvas-specific shaders 106 | ```lua 107 | push:setupCanvas({ { name = "multiple_shaders", shader = { shader1, shader2 } } }) 108 | ``` 109 | 110 | Advanced canvases/shaders 111 | ---------------- 112 | 113 | **push** provides basic canvas and shader functionality through the *canvas* flag in push:setupScreen() and push:setShader(), but you can also create additional canvases, name them for later use and apply multiple shaders to them. 114 | 115 | Set up custom canvases 116 | ```lua 117 | push:setupCanvas(canvasList) 118 | 119 | --e.g. push:setupCanvas({ { name = "foreground", shader = foregroundShader }, { name = "background" } }) 120 | ``` 121 | 122 | Shaders can be passed to canvases directly through push:setupCanvas(), or you can choose to set them later. 123 | ```lua 124 | push:setShader(canvasName, shader) 125 | ``` 126 | 127 | Then, you just need to draw your game on different canvases like you'd do with love.graphics.setCanvas(): 128 | ```lua 129 | push:setCanvas(canvasName) 130 | ``` 131 | 132 | Resizing the window 133 | ---------------- 134 | 135 | In order for push to take in account window resizing (if you have set {resizable = true} in push:setupScreen()), you need to call push:resize() like so: 136 | 137 | ```lua 138 | function love.resize(w, h) 139 | push:resize(w, h) 140 | end 141 | ``` 142 | 143 | Misc 144 | ---------------- 145 | 146 | Switch fullscreen 147 | ```lua 148 | push:switchFullscreen(w, h) 149 | ``` 150 | **w** and **h** are optional parameters that are used in case the game switches to windowed mode 151 | 152 | Set a post-processing shader (will apply to the whole screen) 153 | ```lua 154 | push:setShader([canvasName], shader) 155 | ``` 156 | You don't need to call this every frame. Simply call it once, and it will be stored into **push** until you change it back to something else. 157 | If no canvasName is passed, shader will apply to the final render. Use it at your advantage to combine shader effects. 158 | 159 | Convert coordinates 160 | ```lua 161 | push:toGame(x, y) --convert coordinates from screen to game (useful for mouse position) 162 | --push:toGame will return nil for the values that are outside the game - be sure to check that before using them 163 | 164 | push:toReal(x, y) --convert coordinates from game to screen 165 | ``` 166 | 167 | Get game dimensions 168 | ```lua 169 | push:getDimensions() --returns push:getWidth(), push:getHeight() 170 | 171 | push:getWidth() --returns game width 172 | 173 | push:getHeight() --returns game height 174 | ``` 175 | 176 | Set border color 177 | ```lua 178 | push:setBorderColor(r, g, b, a) --also accepts a table 179 | ``` 180 | 181 | -------------------------------------------------------------------------------- /push.lua: -------------------------------------------------------------------------------- 1 | -- push.lua v0.4 2 | 3 | -- Copyright (c) 2020 Ulysse Ramage 4 | -- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | -- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 7 | 8 | local love11 = love.getVersion() == 11 9 | local getDPI = love11 and love.window.getDPIScale or love.window.getPixelScale 10 | local windowUpdateMode = love11 and love.window.updateMode or function(width, height, settings) 11 | local _, _, flags = love.window.getMode() 12 | for k, v in pairs(settings) do flags[k] = v end 13 | love.window.setMode(width, height, flags) 14 | end 15 | 16 | local push = { 17 | 18 | defaults = { 19 | fullscreen = false, 20 | resizable = false, 21 | pixelperfect = false, 22 | highdpi = true, 23 | canvas = true, 24 | stencil = true 25 | } 26 | 27 | } 28 | setmetatable(push, push) 29 | 30 | function push:applySettings(settings) 31 | for k, v in pairs(settings) do 32 | self["_" .. k] = v 33 | end 34 | end 35 | 36 | function push:resetSettings() return self:applySettings(self.defaults) end 37 | 38 | function push:setupScreen(WWIDTH, WHEIGHT, RWIDTH, RHEIGHT, settings) 39 | 40 | settings = settings or {} 41 | 42 | self._WWIDTH, self._WHEIGHT = WWIDTH, WHEIGHT 43 | self._RWIDTH, self._RHEIGHT = RWIDTH, RHEIGHT 44 | 45 | self:applySettings(self.defaults) --set defaults first 46 | self:applySettings(settings) --then fill with custom settings 47 | 48 | windowUpdateMode(self._RWIDTH, self._RHEIGHT, { 49 | fullscreen = self._fullscreen, 50 | resizable = self._resizable, 51 | highdpi = self._highdpi 52 | }) 53 | 54 | self:initValues() 55 | 56 | if self._canvas then 57 | self:setupCanvas({ "default" }) --setup canvas 58 | end 59 | 60 | self._borderColor = {0, 0, 0} 61 | 62 | self._drawFunctions = { 63 | ["start"] = self.start, 64 | ["end"] = self.finish 65 | } 66 | 67 | return self 68 | end 69 | 70 | function push:setupCanvas(canvases) 71 | table.insert(canvases, { name = "_render", private = true }) --final render 72 | 73 | self._canvas = true 74 | self.canvases = {} 75 | 76 | for i = 1, #canvases do 77 | push:addCanvas(canvases[i]) 78 | end 79 | 80 | return self 81 | end 82 | function push:addCanvas(params) 83 | table.insert(self.canvases, { 84 | name = params.name, 85 | private = params.private, 86 | shader = params.shader, 87 | canvas = love.graphics.newCanvas(self._WWIDTH, self._WHEIGHT), 88 | stencil = params.stencil or self._stencil 89 | }) 90 | end 91 | 92 | function push:setCanvas(name) 93 | if not self._canvas then return true end 94 | local canvasTable = self:getCanvasTable(name) 95 | return love.graphics.setCanvas({ canvasTable.canvas, stencil = canvasTable.stencil }) 96 | end 97 | function push:getCanvasTable(name) 98 | for i = 1, #self.canvases do 99 | if self.canvases[i].name == name then 100 | return self.canvases[i] 101 | end 102 | end 103 | end 104 | function push:setShader(name, shader) 105 | if not shader then 106 | self:getCanvasTable("_render").shader = name 107 | else 108 | self:getCanvasTable(name).shader = shader 109 | end 110 | end 111 | 112 | function push:initValues() 113 | self._PSCALE = (not love11 and self._highdpi) and getDPI() or 1 114 | 115 | self._SCALE = { 116 | x = self._RWIDTH/self._WWIDTH * self._PSCALE, 117 | y = self._RHEIGHT/self._WHEIGHT * self._PSCALE 118 | } 119 | 120 | if self._stretched then --if stretched, no need to apply offset 121 | self._OFFSET = {x = 0, y = 0} 122 | else 123 | local scale = math.min(self._SCALE.x, self._SCALE.y) 124 | if self._pixelperfect then scale = math.floor(scale) end 125 | 126 | self._OFFSET = {x = (self._SCALE.x - scale) * (self._WWIDTH/2), y = (self._SCALE.y - scale) * (self._WHEIGHT/2)} 127 | self._SCALE.x, self._SCALE.y = scale, scale --apply same scale to X and Y 128 | end 129 | 130 | self._GWIDTH = self._RWIDTH * self._PSCALE - self._OFFSET.x * 2 131 | self._GHEIGHT = self._RHEIGHT * self._PSCALE - self._OFFSET.y * 2 132 | end 133 | 134 | function push:apply(operation, shader) 135 | self._drawFunctions[operation](self, shader) 136 | end 137 | 138 | function push:start() 139 | if self._canvas then 140 | love.graphics.push() 141 | love.graphics.setCanvas({ self.canvases[1].canvas, stencil = self.canvases[1].stencil }) 142 | 143 | else 144 | love.graphics.translate(self._OFFSET.x, self._OFFSET.y) 145 | love.graphics.setScissor(self._OFFSET.x, self._OFFSET.y, self._WWIDTH*self._SCALE.x, self._WHEIGHT*self._SCALE.y) 146 | love.graphics.push() 147 | love.graphics.scale(self._SCALE.x, self._SCALE.y) 148 | end 149 | end 150 | 151 | function push:applyShaders(canvas, shaders) 152 | local _shader = love.graphics.getShader() 153 | if #shaders <= 1 then 154 | love.graphics.setShader(shaders[1]) 155 | love.graphics.draw(canvas) 156 | else 157 | local _canvas = love.graphics.getCanvas() 158 | 159 | local _tmp = self:getCanvasTable("_tmp") 160 | if not _tmp then --create temp canvas only if needed 161 | self:addCanvas({ name = "_tmp", private = true, shader = nil }) 162 | _tmp = self:getCanvasTable("_tmp") 163 | end 164 | 165 | love.graphics.push() 166 | love.graphics.origin() 167 | local outputCanvas 168 | for i = 1, #shaders do 169 | local inputCanvas = i % 2 == 1 and canvas or _tmp.canvas 170 | outputCanvas = i % 2 == 0 and canvas or _tmp.canvas 171 | love.graphics.setCanvas(outputCanvas) 172 | love.graphics.clear() 173 | love.graphics.setShader(shaders[i]) 174 | love.graphics.draw(inputCanvas) 175 | love.graphics.setCanvas(inputCanvas) 176 | end 177 | love.graphics.pop() 178 | 179 | love.graphics.setCanvas(_canvas) 180 | love.graphics.draw(outputCanvas) 181 | end 182 | love.graphics.setShader(_shader) 183 | end 184 | 185 | function push:finish(shader) 186 | love.graphics.setBackgroundColor(unpack(self._borderColor)) 187 | if self._canvas then 188 | local _render = self:getCanvasTable("_render") 189 | 190 | love.graphics.pop() 191 | 192 | local white = love11 and 1 or 255 193 | love.graphics.setColor(white, white, white) 194 | 195 | --draw canvas 196 | love.graphics.setCanvas(_render.canvas) 197 | for i = 1, #self.canvases do --do not draw _render yet 198 | local _table = self.canvases[i] 199 | if not _table.private then 200 | local _canvas = _table.canvas 201 | local _shader = _table.shader 202 | self:applyShaders(_canvas, type(_shader) == "table" and _shader or { _shader }) 203 | end 204 | end 205 | love.graphics.setCanvas() 206 | 207 | --draw render 208 | love.graphics.translate(self._OFFSET.x, self._OFFSET.y) 209 | local shader = shader or _render.shader 210 | love.graphics.push() 211 | love.graphics.scale(self._SCALE.x, self._SCALE.y) 212 | self:applyShaders(_render.canvas, type(shader) == "table" and shader or { shader }) 213 | love.graphics.pop() 214 | 215 | --clear canvas 216 | for i = 1, #self.canvases do 217 | love.graphics.setCanvas(self.canvases[i].canvas) 218 | love.graphics.clear() 219 | end 220 | 221 | love.graphics.setCanvas() 222 | love.graphics.setShader() 223 | else 224 | love.graphics.pop() 225 | love.graphics.setScissor() 226 | end 227 | end 228 | 229 | function push:setBorderColor(color, g, b) 230 | self._borderColor = g and {color, g, b} or color 231 | end 232 | 233 | function push:toGame(x, y) 234 | x, y = x - self._OFFSET.x, y - self._OFFSET.y 235 | local normalX, normalY = x / self._GWIDTH, y / self._GHEIGHT 236 | 237 | x = (x >= 0 and x <= self._WWIDTH * self._SCALE.x) and normalX * self._WWIDTH or nil 238 | y = (y >= 0 and y <= self._WHEIGHT * self._SCALE.y) and normalY * self._WHEIGHT or nil 239 | 240 | return x, y 241 | end 242 | 243 | function push:toReal(x, y) 244 | local realX = self._OFFSET.x + (self._GWIDTH * x)/self._WWIDTH 245 | local realY = self._OFFSET.y + (self._GHEIGHT * y)/self._WHEIGHT 246 | return realX, realY 247 | end 248 | 249 | function push:switchFullscreen(winw, winh) 250 | self._fullscreen = not self._fullscreen 251 | local windowWidth, windowHeight = love.window.getDesktopDimensions() 252 | 253 | if self._fullscreen then --save windowed dimensions for later 254 | self._WINWIDTH, self._WINHEIGHT = self._RWIDTH, self._RHEIGHT 255 | elseif not self._WINWIDTH or not self._WINHEIGHT then 256 | self._WINWIDTH, self._WINHEIGHT = windowWidth * .5, windowHeight * .5 257 | end 258 | 259 | self._RWIDTH = self._fullscreen and windowWidth or winw or self._WINWIDTH 260 | self._RHEIGHT = self._fullscreen and windowHeight or winh or self._WINHEIGHT 261 | 262 | self:initValues() 263 | 264 | love.window.setFullscreen(self._fullscreen, "desktop") 265 | if not self._fullscreen and (winw or winh) then 266 | windowUpdateMode(self._RWIDTH, self._RHEIGHT) --set window dimensions 267 | end 268 | end 269 | 270 | function push:resize(w, h) 271 | if self._highdpi then w, h = w / self._PSCALE, h / self._PSCALE end 272 | self._RWIDTH = w 273 | self._RHEIGHT = h 274 | self:initValues() 275 | end 276 | 277 | function push:getWidth() return self._WWIDTH end 278 | function push:getHeight() return self._WHEIGHT end 279 | function push:getDimensions() return self._WWIDTH, self._WHEIGHT end 280 | 281 | return push 282 | --------------------------------------------------------------------------------