{"id":26668288,"url":"https://github.com/oval-tutu/shove","last_synced_at":"2025-04-12T00:42:36.520Z","repository":{"id":284309902,"uuid":"946790676","full_name":"Oval-Tutu/shove","owner":"Oval-Tutu","description":"A powerful resolution-handler and rendering library for LÖVE 📐","archived":false,"fork":false,"pushed_at":"2025-03-25T07:56:33.000Z","size":1648,"stargazers_count":40,"open_issues_count":1,"forks_count":2,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-12T00:42:32.023Z","etag":null,"topics":["aspect-ratio","game-development","gamedev","layers","love2d","love2d-framework","lua","performance","pixel-perfect","rendering","resolution","scaling"],"latest_commit_sha":null,"homepage":"https://oval-tutu.com","language":"Lua","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Oval-Tutu.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":"flexiondotorg"}},"created_at":"2025-03-11T17:18:53.000Z","updated_at":"2025-04-10T20:01:34.000Z","dependencies_parsed_at":"2025-03-25T08:48:38.143Z","dependency_job_id":null,"html_url":"https://github.com/Oval-Tutu/shove","commit_stats":null,"previous_names":["oval-tutu/shove"],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Oval-Tutu%2Fshove","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Oval-Tutu%2Fshove/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Oval-Tutu%2Fshove/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Oval-Tutu%2Fshove/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Oval-Tutu","download_url":"https://codeload.github.com/Oval-Tutu/shove/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248501959,"owners_count":21114681,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["aspect-ratio","game-development","gamedev","layers","love2d","love2d-framework","lua","performance","pixel-perfect","rendering","resolution","scaling"],"created_at":"2025-03-25T20:29:50.198Z","updated_at":"2025-04-12T00:42:36.509Z","avatar_url":"https://github.com/Oval-Tutu.png","language":"Lua","funding_links":["https://github.com/sponsors/flexiondotorg"],"categories":[],"sub_categories":[],"readme":"# Shöve 📐\n**A resolution-handling and rendering library for [LÖVE](https://love2d.org/)**\n\nShöve is a powerful, flexible resolution-handling and rendering library for the LÖVE framework.\nUsing Shöve, you can develop your game at a fixed resolution while scaling to fit the window or screen - all with a simple, intuitive API.\n\n## Why Shöve?\n\nShöve takes the [`dev` branch of push](https://github.com/Ulydev/push/tree/dev), redesigns the API, and builds on its Canvas rendering concept to create a powerful and intuitive library that can handle complex rendering scenarios.\n\n### Key Features\n\n#### 🖼️ Two Powerful Render Modes\n\n- **Direct Mode**: Simple scaling and positioning\n- **Layer Mode**: Advanced rendering with multiple layers\n\n####  📏 Complete Resolution Management\n\n- **Multiple Fit Methods**: Choose from aspect-preserving, pixel-perfect, stretch, or no scaling\n- **Dynamic Resizing**: Responds instantly to window/screen changes\n- **Coordinate Conversion**: Seamlessly map between screen and game coordinates\n\n#### 🥞 Layer-Based Rendering\n\n- **Layer-Based System**: Organize your rendering into logical layers\n- **Z-Order Control**: Easily change which layers appear on top\n- **Visibility Toggling**: Show or hide entire layers with a single call\n- **Complex UIs**: Put your HUD, menus, dialogs, and tooltips on separate layers for easy management.\n- **Integrated Profiling**: Measure performance and debug rendering issues\n\n#### ✨ Effect Pipeline\n\n- **Per-Layer Effects**: Apply shaders to specific layers only\n- **Global Effects**: Transform your entire game with post-processing\n- **Effect Chaining**: Combine multiple shaders for complex visual styles\n- **Smart Masking**: Use any layer as a mask for another\n\n**Shöve offers a progressive learning curve**—start simple and add complexity as needed ‍🧑‍🎓\n\n## Quick Start\n\nHere's a basic example to get started with Shöve.\n\n```lua\nshove = require(\"shove\")\n\nfunction love.load()\n  -- Initialize Shöve with fixed game resolution and options\n  shove.setResolution(400, 300, {fitMethod = \"aspect\"})\n  -- Set up a resizable window\n  shove.setWindowMode(800, 600, {resizable = true})\nend\n\nfunction love.draw()\n  shove.beginDraw()\n    -- Your game here!\n    love.graphics.clear(0.1, 0.1, 0.2)\n    love.graphics.setColor(0.918, 0.059, 0.573)\n    love.graphics.rectangle(\"fill\", 150 + math.sin(love.timer.getTime()) * 150, 100, 100, 100)\n  shove.endDraw()\nend\n\nfunction love.keypressed(key)\n  if key == \"escape\" then love.event.quit() end\nend\n```\n\nYou can now draw your game at a fixed resolution and have it scale to fit the window.\n\n**💡 NOTE!** That is all you need to get started! **Everything else that follows is optional**, but very tasty 👅\n\n# Demos ️🕹️\n\nRun `love demo/` to explore all demos. Use \u003ckbd\u003eSPACE\u003c/kbd\u003e to cycle through examples, \u003ckbd\u003ef\u003c/kbd\u003e to toggle fullscreen, and \u003ckbd\u003eESC\u003c/kbd\u003e to exit.\n\nEach demo showcases different Shöve capabilities:\n- [**low-res**](https://github.com/Oval-Tutu/shove/blob/main/demo/low-res/init.lua):: Pixel-perfect scaling with a tiny 64x64 resolution\n- [**single-shader**](https://github.com/Oval-Tutu/shove/blob/main/demo/single-shader/init.lua): Apply shaders to specific layers\n- [**multiple-shaders**](https://github.com/Oval-Tutu/shove/blob/main/demo/multiple-shaders/init.lua): Chain multiple effects\n- [**mouse-input**](https://github.com/Oval-Tutu/shove/blob/main/demo/mouse-input/init.lua): Convert between screen and viewport coordinates\n- [**canvases-shaders**](https://github.com/Oval-Tutu/shove/blob/main/demo/canvases-shaders/init.lua): Apply different effects to different layers\n- [**stencil**](https://github.com/Oval-Tutu/shove/blob/main/demo/stencil/init.lua): Use stencil buffers with layers\n- [**mask**](https://github.com/Oval-Tutu/shove/blob/main/demo/mask/init.lua): Create dynamic visibility with layer masking\n- [**parallax**](https://github.com/Oval-Tutu/shove/blob/main/demo/parallax/init.lua): Multi-layered background scrolling with animated particles and bloom effects\n\n# Shöve Guide 📚\n\nThis guide provides documentation for using Shöve.\n\n## Installation\n\nPlace `shove.lua` in your project directory and require it in your code:\n\n```lua\nshove = require(\"shove\")\n```\n\n## Basic Concepts\n\nShöve provides a system for rendering your game at a fixed resolution while scaling to fit to the window or screen.\n\n- **Viewport**: The fixed resolution area where your game is drawn\n- **Screen**: The window or screen where the game is displayed\n- **Layers**: Separate rendering surfaces that can be manipulated independently\n  - **Layer Masks**: Layers that control visibility of other layers\n- **Shaders**: [GLSL](https://www.khronos.org/opengl/wiki/Core_Language_(GLSL)) programs that transform pixel colors\n  - **Layer Effects**: Shaders applied to specific layers\n  - **Persistent Global Effects**: Shaders applied to the final composited output\n  - **Transient Global Effects**: Shaders applied to the final output for a single frame\n\n## Fit Methods and Scaling\n\nShöve offers several methods to fit your game to different screen sizes:\n\n- **aspect**: Maintains aspect ratio, scales as large as possible (*default*)\n- **pixel**: Integer scaling only, for pixel-perfect rendering\n- **stretch**: Stretches to fill the entire window\n- **none**: No scaling, centered in the window\n\n```lua\n-- Use pixel-perfect scaling\nshove.setResolution(320, 240, {fitMethod = \"pixel\"})\n```\n\n### Scaling Filters\n\nThe `scalingFilter` option determines how textures are scaled when rendering your game. Here's how it works:\n\nYou can set `scalingFilter` in two ways:\n\n1. **At initialization:**\n   ```lua\n   shove.setResolution(800, 600, {\n     fitMethod = \"aspect\",\n     scalingFilter = \"nearest\" -- Set filtering explicitly\n   })\n   ```\n\n2. **At runtime:**\n   ```lua\n   shove.setScalingFilter(\"linear\")\n   ```\n\nIf `scalingFilter` is not explicitly specified, Shöve automatically selects a default based on your `fitMethod`:\n\nWith `fitMethod = \"pixel\"` it defaults to `\"nearest\"` filtering to preserve pixel-perfect appearance. With all other fit methods (`\"aspect\"`, `\"stretch\"`, `\"none\"`) it defaults to `\"linear\"` filtering for smoother scaling.\n\n- `nearest`: Nearest-neighbor filtering (sharp, pixel-perfect, no interpolation)\n- `linear`: Linear filtering (smooth blending between pixels)\n\nHere are the typical use cases:\n\n- Use `\"nearest\"` when creating pixel art games where you want to preserve the crisp edges of your pixels\n- Use `\"linear\"` for smoother visuals with games that use higher resolution assets\n- Override the default when you need a specific visual style regardless of fit method\n\nYou can check the current setting at any time with `shove.getScalingFilter()`.\n\n## Window Management\n\nShöve provides wrapper functions for LÖVE's window management.\nSet the window dimensions and properties with automatic resize handling using `shove.setWindowMode(width, height, flags)`.\n\n**💡 NOTE:** For best results, call `shove.setResolution()` before using these window management functions to ensure proper viewport initialization.\n\n\n```lua\n-- Create a window half the size of the desktop\nlocal desktopWidth, desktopHeight = love.window.getDesktopDimensions()\nshove.setWindowMode(desktopWidth * 0.5, desktopHeight * 0.5, {\n  resizable = true,\n  vsync = true,\n  minwidth = 400,\n  minheight = 300\n})\n```\n\nUse `shove.updateWindowMode(width, height, flags)` to change the window size and properties.\n\n## Render Modes\n\n### Direct Rendering\n\nDirect rendering is simple and lightweight.\nIt's suitable for games that don't need advanced rendering features.\n\n**💡NOTE!** With direct rendering enabled, none of the layer rendering or effects functions are available.\n\n```lua\n-- Initialize with direct rendering mode\nshove.setResolution(960, 540, {renderMode = \"direct\"})\n\nfunction love.draw()\n  shove.beginDraw()\n    -- All drawing operations are directly scaled and positioned\n    love.graphics.setColor(1, 0, 0)\n    love.graphics.rectangle(\"fill\", 100, 100, 200, 200)\n\n    -- Drawing happens on a single surface\n    love.graphics.setColor(0, 0, 1)\n    love.graphics.circle(\"fill\", 400, 300, 50)\n  shove.endDraw()\nend\n```\n\n### Layer-Based Rendering\n\nThink of Shöve's layers like Photoshop or Figma layers—separate \"sheets\" that combine to create your complete scene.\n\n\n```lua\n-- Traditional approach (harder to manage)\nfunction love.draw()\n  drawBackground()\n  drawCharacters()\n  drawParticles()\n  drawUI()\n  if debugMode then drawDebugInfo() end\nend\n```\n\nWith Shöve's layers, you can organize these logically and manage them independently:\n\n```lua\n-- Layer approach (more flexible and organized)\nshove.beginDraw()\n  shove.beginLayer(\"background\")\n    drawBackground()\n  shove.endLayer()\n\n  shove.beginLayer(\"gameplay\")\n    drawCharacters()\n    drawParticles()\n  shove.endLayer()\n\n  shove.beginLayer(\"ui\")\n    drawUI()\n  shove.endLayer()\n\n  -- Only rendered when debug mode is on\n  shove.beginLayer(\"debug\")\n    if debugMode then\n      shove.showLayer(\"debug\")\n      drawDebugInfo()\n    else\n      shove.hideLayer(\"debug\")\n    end\n  shove.endLayer()\nshove.endDraw()\n```\n\n#### Key Benefits\n\nMany of the benefits of Shöve's layers are similar to those in professional creative software:\n\n- **Independent Control:** Hide, show, or modify layers without affecting others\n- **Z-Ordering:** Change which elements appear on top\n- **Effect Application:** Apply shaders to specific layers\n- **Visual Debugging:** Toggle debug visualization on/off\n- **State Management:** Control entire game states through layer visibility\n\nLayer rendering provides powerful features for organizing your rendering into separate layers that can be manipulated independently.\nUnder the hood, Shöve uses [LÖVE's Canvas system](https://love2d.org/wiki/Canvas) to achieve this, but hides the complexity behind a simple API.\n\n```lua\nshove = require(\"shove\")\n-- Initialize with layer rendering mode\nshove.setResolution(800, 600, {renderMode = \"layer\"})\n\nfunction love.load()\n  -- Create some layers (optional, they're created automatically when used)\n  shove.createLayer(\"background\")\n  shove.createLayer(\"entities\")\n  shove.createLayer(\"ui\", {zIndex = 10}) -- Higher zIndex renders on top\nend\n\nfunction love.draw()\n  shove.beginDraw()\n    -- Draw to the background layer\n    shove.beginLayer(\"background\")\n      love.graphics.setColor(0.2, 0.3, 0.8)\n      love.graphics.rectangle(\"fill\", 0, 0, 800, 600)\n    shove.endLayer()\n\n    -- Draw to the entities layer\n    shove.beginLayer(\"entities\")\n      love.graphics.setColor(1, 1, 1)\n      love.graphics.circle(\"fill\", 400, 300, 50)\n    shove.endLayer()\n\n    -- Draw to the UI layer\n    shove.beginLayer(\"ui\")\n      love.graphics.setColor(1, 0.8, 0)\n      love.graphics.print(\"Score: 100\", 20, 20)\n    shove.endLayer()\n  shove.endDraw()\nend\n```\n\n## Coordinate Handling\n\nShöve provides functions to convert between screen and game viewport coordinates:\n\n```lua\nfunction love.mousepressed(screenX, screenY, button)\n  -- Convert screen coordinates to viewport coordinates\n  local inViewport, gameX, gameY = shove.screenToViewport(screenX, screenY)\n\n  if inViewport then\n    -- Mouse is inside the game viewport\n    handleClick(gameX, gameY, button)\n  end\nend\n\n-- Get mouse position directly in viewport coordinates\nfunction love.update(dt)\n  local mouseInViewport, mouseX, mouseY = shove.mouseToViewport()\n  if inside then\n    player:aimToward(mouseX, mouseY)\n  end\nend\n\n-- Convert viewport coordinates back to screen coordinates\nfunction drawScreenUI()\n  local screenX, screenY = shove.viewportToScreen(playerX, playerY)\n  -- Draw something at the screen position\nend\n```\n\n## Layer Management\n\nWhile Shöve automatically creates layers when you first draw to them after declaring them with `beginLayer()`, there are several compelling reasons to manually create layers with `createLayer()` instead:\n\n```lua\n-- Create a layer with specific properties\nshove.createLayer(\"ui\", {\n  zIndex = 100,      -- Ensure UI is always on top\n  visible = false,   -- Start hidden until needed\n  stencil = true     -- Enable stencil support\n})\n```\n\nManual creation lets you configure layers with specific options from the start, rather than using defaults and modifying later.\nPre-defining your layers creates a clear \"blueprint\" of your rendering architecture:\n\n```lua\nfunction initLayers()\n  -- Background layers\n  shove.createLayer(\"sky\", {zIndex = 10})\n  shove.createLayer(\"mountains\", {zIndex = 20})\n  shove.createLayer(\"clouds\", {zIndex = 25})\n\n  -- Gameplay layers\n  shove.createLayer(\"terrain\", {zIndex = 30})\n  shove.createLayer(\"entities\", {zIndex = 40})\n  shove.createLayer(\"particles\", {zIndex = 50})\n\n  -- UI layers\n  shove.createLayer(\"hud\", {zIndex = 100})\n  shove.createLayer(\"menu\", {zIndex = 110})\n  shove.createLayer(\"debug\", {zIndex = 1000, visible = debugMode})\nend\n```\n\nThis approach documents your rendering pipeline and makes relationships between layers clear.\nManual creation allows you to configure layer relationships before any drawing occurs:\n\n```lua\n-- Set up mask relationships at initialization\nshove.createLayer(\"lightning_mask\", {stencil = true})\nshove.createLayer(\"foreground\")\nshove.setLayerMask(\"foreground\", \"lightning_mask\")\n\n-- Apply initial effects\nshove.createLayer(\"underwater\")\nshove.addEffect(\"underwater\", waterDistortionShader)\n```\n\nCreating all layers upfront improves predictability:\n\n- All canvases are allocated at once rather than during gameplay\n- Memory usage is more consistent\n- No canvas creation overhead during rendering\n- Dynamic layer creation can cause hitching or frame drops when over used\n\n```lua\nfunction love.load()\n  -- Game setup\n  setupEntities()\n  loadResources()\n\n  -- Define our rendering architecture upfront\n  shove.createLayer(\"background\", {zIndex = 10})\n  shove.createLayer(\"middleground\", {zIndex = 20})\n  shove.createLayer(\"entities\", {zIndex = 30})\n  shove.createLayer(\"particles\", {zIndex = 40})\n  shove.createLayer(\"ui\", {zIndex = 100})\n\n  -- Configure special properties\n  shove.addEffect(\"background\", parallaxEffect)\n  shove.createLayer(\"mask_layer\", {stencil = true})\n  shove.setLayerMask(\"particles\", \"mask_layer\")\nend\n```\n\nWith this approach, your rendering architecture is clearly defined, properly configured, and ready to use before your first frame is drawn.\n\n## Blend Modes\n\nShove provides full support for LÖVE's blend modes at the layer level. This gives you precise control over how layers blend with each other when composited.\n\n### Blend Mode Constants\n\nFor convenience and better code readability, Shove provides constants for all available blend modes:\n\n```lua\n-- Use constants instead of string literals\nshove.setLayerBlendMode(\"particles\", shove.BLEND.ADD)\n\n-- Available blend mode constants\nshove.BLEND.ALPHA    -- Normal alpha blending (default)\nshove.BLEND.REPLACE  -- Replace pixels without blending\nshove.BLEND.SCREEN   -- Screen blending (lightens)\nshove.BLEND.ADD      -- Additive blending (glow effects)\nshove.BLEND.SUBTRACT -- Subtractive blending\nshove.BLEND.MULTIPLY -- Multiply colors (darkening)\nshove.BLEND.LIGHTEN  -- Keep lighter colors\nshove.BLEND.DARKEN   -- Keep darker colors\n\n-- Alpha mode constants\nshove.ALPHA.MULTIPLY     -- Standard alpha multiplication (default)\nshove.ALPHA.PREMULTIPLIED -- For pre-multiplied alpha content\n```\n\n## Blend Modes\n\nYou can set blend modes either during layer creation or at any time afterward:\n\n```lua\n-- Set blend mode during layer creation\nshove.createLayer(\"glow\", {\n  zIndex = 50,\n  blendMode = shove.BLEND.ADD,      -- Additive blending\n  blendAlphaMode = shove.ALPHA.MULTIPLY -- Default alpha mode\n})\n\n-- Set blend mode for an existing layer\nshove.setLayerBlendMode(\"particles\", shove.BLEND.ADD)\nshove.setLayerBlendMode(\"ui\", shove.BLEND.ALPHA, shove.ALPHA.PREMULTIPLIED)\n\n-- Get current blend modes\nlocal blendMode, alphaMode = shove.getLayerBlendMode(\"particles\")\n```\n\n### Common Blend Mode Use Cases\n\nDifferent blend modes enable various visual effects:\n\n- **ADD**: Perfect for glowing effects, particle systems, light sources\n  ```lua\n  shove.setLayerBlendMode(\"fire\", shove.BLEND.ADD)\n  ```\n- **MULTIPLY**: Great for shadows and darkening effects\n  ```lua\n  shove.setLayerBlendMode(\"shadow\", shove.BLEND.MULTIPLY)\n  ```\n- **SCREEN**: Useful for lightning, lasers, and brightening effects\n  ```lua\n  shove.setLayerBlendMode(\"lightning\", shove.BLEND.SCREEN)\n  ```\n- **ALPHA**: Standard transparency blending (default)\n  ```lua\n  shove.setLayerBlendMode(\"ui\", shove.BLEND.ALPHA)\n  ```\n\nFor proper rendering of content drawn to canvases, Shöve automatically uses premultiplied alpha when compositing layers, while respecting each layer's blend mode setting.\n\n## Layer Masking\n\nLayer masking in Shöve provides a straightforward way to control visibility between layers.\nThe masking system uses one layer's content to determine which parts of another layer are visible.\n\nBehind the scenes, Shöve's layer masking system works through these steps:\n\n1. **Mask Layer Creation**: A layer is created that will serve as the mask\n2. **Mask Content Drawing**: Content is drawn to this layer (typically shapes or patterns)\n3. **Mask Assignment**: The `shove.setLayerMask(\"targetLayer\", \"maskLayer\")` function assigns the relationship\n4. **Rendering Process**:\n   - When the target layer is drawn, Shöve detects it has a mask assigned\n   - Shöve converts the mask layer's content into an alpha mask\n   - The target layer is only visible where the mask layer has non-transparent pixels\n\nBehind the scenes, Shöve uses [LÖVE's stencil system](https://love2d.org/wiki/love.graphics.stencil) and automatically manages the stencil buffer and shader masks for you.\n\n```lua\n-- Create a mask layer\nshove.beginDraw()\n  shove.beginLayer(\"mask\")\n    -- Draw shapes to define the visible area\n    love.graphics.circle(\"fill\", 400, 300, 100)\n  shove.endLayer()\n\n  -- Set the mask\n  shove.setLayerMask(\"content\", \"mask\")\n\n  -- Draw content that will be masked\n  shove.beginLayer(\"content\")\n    -- This will only be visible inside the circle\n    drawComplexScene()\n  shove.endLayer()\nshove.endDraw()\n```\n\nShöve's layer masking offers an elegant abstraction over LÖVE's stencil buffer, trading some low-level flexibility for ease of use and integration with the layer-based rendering architecture.\n\n1. **Simplified API**: Layer masks provide a straightforward, higher-level API that doesn't require understanding stencil buffer mechanics\n   ```lua\n   shove.setLayerMask(\"content\", \"mask\")\n   ```\n   versus\n   ```lua\n   love.graphics.stencil(stencilFunction, \"replace\", 1)\n   love.graphics.setStencilTest(\"greater\", 0)\n   -- Draw content\n   love.graphics.setStencilTest()\n   ```\n2. **Persistent Relationship**: The mask relationship stays in effect until changed, requiring no repeated setup each frame\n3. **Dynamic Masking**: The mask layer can be animated or changed over time, and the masking relationship automatically updates\n4. **Layer Management Integration**: Masks inherit all layer system benefits like z-ordering, visibility toggling, and effects\n5. **Reusability**: A single mask layer can be used to mask multiple target layers\n6. **Declarative Style**: The mask relationship is defined separately from drawing operations, leading to cleaner, more maintainable code\n\nAlthough layer masks provide a high-level API for masking, there are scenarios where manual stencil buffer manipulation might be more appropriate and Shöve supports direct access to the stencil buffer for advanced use cases.\n\nThe layer mask approach separates the mask definition from its application, resulting in more modular, maintainable code that follows a declarative programming style. The stencil approach gives more immediate control but requires more technical knowledge and careful state management.\n\n## Effect System\n\nShöve includes a powerful effect system for applying [Shaders](https://love2d.org/wiki/Shader) to layers or the final output.\n\nThe effect system is designed to be efficient by:\n\n- Only creating temporary canvases when needed\n- Resizing canvases only when the viewport changes\n- Applying effects only to visible layers\n- Only processing active effects\n\n**💡NOTE!** Each additional effect requires more GPU processing. **Complex shaders or many effects can impact performance**.\n\n### Layer Effects\n\nLayer effects provide a powerful way to apply shader-based visual effects to specific layers independently.\nThis creates a flexible rendering pipeline where different parts of your scene can have unique visual treatments.\n\nLayer effects provide a clean abstraction over LÖVE's shader system that integrates with the layer-based rendering architecture, giving you powerful visual capabilities with a simple API. Here's how it works:\n\n1. Shöve checks if the specified layer exists, creating it if necessary\n2. Shöve verifies that the layer's internal structure includes an `effects` table\n3. The shader is added to this `effects` table for the layer\n4. Effects are stored in order of addition, which determines their application sequence\n\nDuring the rendering process, here's what happens:\n\n1. When `beginLayer()` is called, Shöve sets the current active layer\n2. All drawing commands between `beginLayer()` and `endLayer()` are captured on the layer's canvas\n3. When `endLayer()` is called, Shöve checks if the layer has any effects\n4. If effects exist, each is applied sequentially to the layer's canvas\n5. The effects processing occurs before the layer is composited with other layers\n\nEach effect's shader transforms the entire layer canvas, not individual drawing operations.\nThis means that all content drawn to a layer is processed together by its effects.\n\n```lua\n-- Create some shaders\nlocal blurShader = love.graphics.newShader(\"blur.glsl\")\nlocal waveShader = love.graphics.newShader(\"wave.glsl\")\n\n-- Add effects to specific layers\nshove.addEffect(\"water\", waveShader)\nshove.addEffect(\"background\", blurShader)\n\n-- Remove an effect\nshove.removeEffect(\"background\", blurShader)\n\n-- Clear all effects from a layer\nshove.clearEffects(\"water\")\n```\n\nWhen multiple effects are added to a layer, they form a processing chain:\n\n- The original content is drawn to a temporary canvas\n- The first effect processes this canvas, outputting to another canvas\n- The second effect takes that output as input, processing to yet another canvas\n- This continues through all effects in the layer's effect list\n- The final processed canvas becomes the layer's output\n- This approach allows effects to build upon each other, creating complex visual treatments that wouldn't be possible with a single shader.\n\n### Global Effects\n\nIn Shöve, global effects are shaders applied to the final composite image after all layers have been rendered and combined.\nThey affect the entire viewport output rather than individual layers.\nThis is implemented using LÖVE's shader system, which processes the pixels of a canvas through a GLSL shader program.\n\nWhen you apply global effects, here's what happens under the hood:\n\n- All layers are first rendered to their individual canvases\n- These layer canvases are composited together in z-order to a final canvas\n- The global effects are then applied to this final canvas\n- The resulting image is scaled and positioned according to the fit method\n- Finally, the processed image is drawn to the screen\n\n```lua\n-- Apply effects to the final composited output\nlocal bloomShader = love.graphics.newShader(\"bloom.glsl\")\n\n-- Persistent: Set up persistent global effects, most common use case\nshove.addGlobalEffect(bloomShader)\n\n-- Transient: Apply a transient global effect for a single frame\nshove.beginDraw()\n  -- Draw content\nshove.endDraw({bloomShader})\n```\n\nFor most use cases requiring consistent effects, `addGlobalEffect` is the cleaner approach.\nFor dynamic or temporary effects, passing shaders directly to `endDraw` provides more flexibility.\n\n#### Persistent: Using `addGlobalEffect(bloomShader)`\n\nThis method registers the shader as a **persistent global effect**. In the implementation:\n\n1. The shader is added to an internal table of global effects\n2. It's automatically applied during every subsequent call to `endDraw`\n3. The effect persists until explicitly removed with `removeGlobalEffect` or cleared with `clearGlobalEffects`\n4. These persistent effects are applied before any transient effects passed to `endDraw`\n\nThis approach is better for:\n- Consistent visual effects that should apply across multiple frames\n- Effects that you want to toggle on and off programmatically\n- When you need to manage multiple global effects that are applied consistently\n\n#### Transient: Passing Effects to `endDraw({bloomShader})`\n\nThis method applies the shader(s) **only for the current frame**, when you pass shaders to `endDraw`:\n\n1. Shöve takes the array of shaders you provide\n2. It applies them in sequence after compositing all layers\n3. The shaders are used just once and don't persist to the next frame\n4. These one-time effects are applied **after** any persistent global effects\n\nThis approach is useful for:\n- Effects that you want to apply only temporarily\n- Visual transitions that should last just one frame\n- Dynamic effects where you need to create new shader instances each frame\n\n### Chaining Effects\n\nWhen multiple effects are added to a layer or set globally, they're applied in sequence:\n\n```lua\n-- Create a chain of effects\nlocal effects = {\n  love.graphics.newShader(\"grayscale.glsl\"),\n  love.graphics.newShader(\"vignette.glsl\"),\n  love.graphics.newShader(\"scanlines.glsl\")\n}\n\n-- Apply the chain to a layer\nfor _, effect in ipairs(effects) do\n  shove.addEffect(\"final\", effect)\nend\n```\n\n## Advanced Techniques\n\n### Drawing to Layers with Callbacks\n\n`drawOnLayer()` provides a convenient way to temporarily switch to a different layer, perform drawing operations, and then automatically return to the previous layer - all without disrupting your main drawing flow.\nIt elegantly handles all the layer switching mechanics, allowing you to focus on your drawing code rather than layer management.\n\nHow it Works:\n1. **Validates context**: Checks if rendering is in \"layer\" mode and we're currently in an active drawing cycle.\n2. **Preserves state**: Saves the currently active layer.\n3. **Switches context**: Activates the target layer.\n4. **Executes callback**: Runs your drawing function on that layer.\n5. **Restores context**: Returns to the previous layer (or ends layer drawing if there was no previous layer).\n\nExample usage:\n\n```lua\nshove.beginDraw()\n  -- Draw main content\n  shove.beginLayer(\"game\")\n    drawGameWorld()\n  shove.endLayer()\n\n  -- Draw something to a specialized layer with a callback\n  shove.drawOnLayer(\"particles\", function()\n    spawnExplosionParticles(x, y)\n  end)\n\n  -- Continue with normal drawing flow\n  shove.beginLayer(\"ui\")\n    drawUI()\n  shove.endLayer()\nshove.endDraw()\n```\n\nHere are some good use cases for `drawOnLayer()`:\n\n1. **Isolated drawing tasks**: When you need to draw to multiple layers but want to keep your code organized.\n2. **Reusable drawing functions**: Create modular drawing functions that can be applied to any layer.\n3. **Dynamic UI elements**: Draw UI components (like tooltips or notifications) to their own layers without breaking your main drawing flow.\n4. **Temporary effects**: Draw short-lived visual effects to dedicated layers.\n5. **State-based drawing**: Switch layers based on game state without complex conditional logic.\n\n### Manual Compositing\n\nThe `drawComposite()` function performs an intermediate composite and draw operation during an active drawing cycle.\nSpecifically, it:\n\n1. Takes all layers that have been drawn so far in the current frame\n2. Composites these layers together according to their z-index ordering\n3. Applies transient global effects that are passed as an argument\n4. Applied persistent global effects only when specifically requested\n5. Renders this composite using the configured fit method\n6. Critically, **it does not end the drawing process**, allowing further layers to be drawn afterward\n\nThis differs from the typical `beginDraw()`/`endDraw()` cycle, where compositing and drawing only happen at the end when `endDraw()` is called.\n\nThe `drawComposite()` function provides a powerful tool for advanced rendering techniques.\nIt gives you finer control over the rendering pipeline by allowing intermediate compositing and drawing operations within a single frame.\n\n- `drawComposite()` → Composite and draw the current state with no transient or persistent effects\n- `drawComposite({anEffect}, false)` → Composite and draw the current state with a transient effect\n- `drawComposite({anEffect, anotherEffect}, true)` → Composite and draw the current state with transient and persistent effects\n- `drawComposite(nil, true)` → Composite and draw the current state with persistent effects\n\nWhile most games won't need this level of control, it can be useful for complex visual effects, multi-stage rendering, debugging, or interactive applications that need to respond to partially-rendered content.\nYou can manually trigger the compositing process before the end of drawing:\n\n### When to Use `drawComposite()`\n\n#### Multi-Pass Rendering\n\n```lua\nshove.beginDraw()\n  -- Draw world and characters\n  shove.beginLayer(\"world\")\n    drawWorld()\n  shove.endLayer()\n\n  shove.beginLayer(\"characters\")\n    drawCharacters()\n  shove.endLayer()\n\n  -- Composite and draw what we have so far\n  shove.drawComposite()\n\n  -- Draw second pass with effects that need to see the first pass result\n  shove.beginLayer(\"lighting\")\n    drawDynamicLighting() -- This might use rendered result as input\n  shove.endLayer()\n\n  shove.beginLayer(\"ui\")\n    drawUserInterface()\n  shove.endLayer()\nshove.endDraw()\n```\n\n#### Visual Debugging\n\n```lua\nshove.beginDraw()\n  -- Draw base layers\n\n  -- Show intermediate result for debugging\n  shove.drawComposite()\n\n  -- Debug visualization appears on top\n  shove.beginLayer(\"debug\")\n    drawCollisionBoxes()\n    drawPathfindingGrid()\n  shove.endLayer()\nshove.endDraw()\n```\n\n#### Interactive Layer Building\n\nFor cases where layers depend on previous composite results:\n\n```lua\nshove.beginDraw()\n  -- Draw background layers\n\n  -- Draw to screen so we can capture player input on what's been drawn so far\n  shove.drawComposite()\n\n  -- Get player input based on what they see\n  local selectedPosition = getPlayerSelection()\n\n  -- Continue drawing with new information\n  shove.beginLayer(\"selection\")\n    drawSelectionHighlight(selectedPosition)\n  shove.endLayer()\nshove.endDraw()\n```\n\nManual compositing has some advantages and considerations:\n\n#### Advantages:\n\n- Enables more complex rendering pipelines\n- Allows for effects that need to see intermediate results\n- Supports interactive feedback during rendering\n- Can help with memory management for complex scenes\n- Provides a way to debug rendering issues\n\n#### Considerations:\n\n- Multiple composites in a single frame can affect performance\n- Each call creates additional draw operations\n- May complicate the rendering logic and make code harder to follow\n- Generally not needed for simple rendering scenarios\n\n### Resize Callbacks\n\nShöve provides a resize callback system that allows you to register functions that automatically run after window resize events.\nThis is useful for adapting UI layouts, recreating canvases, and handling other resize-dependent operations.\n\nUse `shove.setResizeCallback()` to register a function to be called after resolution transforms are recalculated during resize operations.\n\n```lua\nshove.setResizeCallback(function(width, height)\n  -- width and height are the new window dimensions\n  -- Resize-dependent code here\nend)\n```\n\n`shove.getResizeCallback()` can be used to retrieve the currently registered resize callback function.\n\nIf you need multiple resize handlers, you can implement your own dispatch system:\n```lua\nlocal resizeHandlers = {}\n\nlocal function masterResizeCallback()\n  for _, handler in ipairs(resizeHandlers) do\n    handler()\n  end\nend\n\n-- Set up the master callback\nshove.setResizeCallback(masterResizeCallback)\n\n-- Add handlers as needed\nfunction addResizeHandler(handler)\n  table.insert(resizeHandlers, handler)\nend\n```\n\n### Performance Profiler\n\nShöve includes a built-in performance profiler that provides real-time information about resolution management, layer status, and rendering performance.\nThis can help diagnose scaling issues and optimize your game during development.\nThe profiler does not have any active code paths active until you enable it, so it has zero impact on performance when hidden.\n\nThe profiler overlay displays:\n- Hardware information (OS, CPU, GPU)\n- Performance metrics (FPS, draw calls, memory usage)\n- Resolution and scaling information\n- Detailed layer information (when using layer rendering)\n\nThe profiler is rendered after at the end of `shove.endDraw()` and can be toggled on/off with a keyboard shortcut.\n\n```lua\n-- The profiler loads automatically when the library initializes\nfunction love.draw()\n  shove.beginDraw()\n  -- Your drawing code here\n  shove.endDraw()\n  -- ** profiler rendered at the end of shove.endDraw() here **\nend\n```\n\nWhen evaluating batch processing impact, pay attention to:\n\n1. **State Changes**: Fewer is better, especially on mobile\n2. **Batch Groups**: Number of layer groups with similar properties\n3. **Batched Layers**: Total layers processed in batches\n4. **Batched Effect Operations**: How many effect applications were optimized\n\n#### Profiler Controls\n\n- **Keyboard:** \u003ckbd\u003eCtrl\u003c/kbd\u003e + \u003ckbd\u003eP\u003c/kbd\u003e to toggle overlay\n- **Keyboard:** \u003ckbd\u003eCtrl\u003c/kbd\u003e + \u003ckbd\u003eT\u003c/kbd\u003e to toggle FPS overlay\n- **Keyboard:** \u003ckbd\u003eCtrl\u003c/kbd\u003e + \u003ckbd\u003eV\u003c/kbd\u003e to toggle VSync (when overlay is visible)\n- **Keyboard:** \u003ckbd\u003eCtrl\u003c/kbd\u003e + \u003ckbd\u003eS\u003c/kbd\u003e to toggle overlay size\n- **Controller:** Select + A/Cross to toggle overlay\n- **Controller:** Select + B/Circle to toggle VSync (when overlay is visible)\n- **Controller:** Select + Y/Triangle to toggle overlay size\n- **Touch:** Double-tap top right corner to toggle overlay\n- **Touch:** Double-tap the overlay to toggle VSync (when overlay is visible)\n- **Touch:** Double-tap overlay panel border/edge to toggle overlay size\n\n#### Profiler Performance Considerations\n\nWhen running at very high frame rates (many hundred of FPS), the profiler itself introduces a small but measurable performance overhead.\nIn our testing we observed the profiler's impact to be approximately 1.2% to 1.5% of total FPS.\nThis overhead comes from the additional calculations, memory access, and UI rendering that the profiler performs each frame to track and display metrics.\n\nFor most development scenarios, this minimal impact won't affect your workflow. However, when performing precise performance benchmarking or optimization on high-end systems, consider temporarily disabling the profiler by using the toggle shortcut (\u003ckbd\u003eCtrl\u003c/kbd\u003e + \u003ckbd\u003eP\u003c/kbd\u003e) or removing the profiler module entirely for the most accurate measurements.\n\n#### Disabling the Profiler for Production\n\nThe profiler is implemented in a separate file (`shove-profiler.lua`) so you can easily disable it in production builds.\nRemove profiler file in your production builds and Shöve will automatically detect its absence and use a no-op stub implementation.\nThis approach ensures that the profiler adds zero overhead to your game in production releases while exposing useful tooling during development.\n\n## Optimizing Layer Rendering\n\nShöve implements several sophisticated rendering optimizations that can help improve performance when using layer-based rendering. These optimizations focus on reducing state changes and minimizing draw calls.\n\n### Key Optimizations\n\n#### Layer Batching\n\nThe core optimization in Shöve is layer batching, which:\n\n1. **Groups similar layers** based on shared properties (blend mode, effects, masks)\n2. **Processes effects in batches** to reduce shader switches\n3. **Minimizes state changes** by setting blend modes once per batch\n4. **Optimizes memory usage** with persistent table reuse\n\nThese optimizations are particularly valuable for:\n- Games with many layers using the same blend mode\n- Scenes with multiple layers sharing identical effects\n- Lower-end hardware where state changes are expensive\n- Mobile devices where reducing draw calls improves battery life\n\n### Controlling Batch Processing\n\nBatch processing is enabled by default for layer-based render but can be toggled at runtime:\n\n```lua\n-- Disable batch processing\nlocal previousState = shove.setLayerBatching(false)\n\n-- Check current batch processing state\nlocal batchingEnabled = shove.getLayerBatching()\n\n-- Re-enable batch processing\nshove.setLayerBatching(true)\n```\n\n#### When to Disable Batching\n\nDespite its benefits, batch processing adds some CPU overhead. Consider disabling it when:\n\n- Your game has very few layers (less than 3-4)\n- Layers have unique blend modes or effects (no batching opportunities)\n- You're CPU-bound rather than GPU-bound\n- Profiler metrics show no significant reduction in state changes\n\n#### Testing Your Specific Case\n\nSince rendering performance is highly dependent on your specific game and target hardware, use the profiler to test both modes:\n1. Run your game with default settings (batching enabled)\n2. Note the FPS and state change metrics\n3. Disable batching: `shove.setLayerBatching(false)`\n4. Compare metrics to determine the best configuration\n\nThe ideal setting varies by game, so let your profiler results guide your decision rather than assuming one approach is always better.\n\n## API Reference\n\n### Initialization and Setup\n\n- `shove.setResolution(width, height, options)` - Initialize with game resolution\n- `shove.setWindowMode(width, height, options)` - Set window mode\n- `shove.updateWindowMode(width, height, options)` - Update window mode\n- `shove.resize(width, height)` - Update when window size changes\n\n### Drawing Flow\n\n- `shove.beginDraw()` - Start drawing operations\n- `shove.endDraw(globalEffects)` - End drawing and display result\n\n### Coordinate Handling\n\n- `shove.isInViewport(x, y)` - Check if coordinates are inside viewport\n- `shove.mouseToViewport()` - Convert mouse position to viewport\n- `shove.screenToViewport(x, y)` - Convert screen coordinates to viewport\n- `shove.viewportToScreen(x, y)` - Convert viewport coordinates to screen\n\n### Utility Functions\n\n- `shove.getFitMethod()` - Get current fit method\n- `shove.setFitMethod(fitMethod)` - Set fit method\n- `shove.getRenderMode()` - Get current render mode\n- `shove.setRenderMode(renderMode)` - Set render mode\n- `shove.getScalingFilter()` - Get current scaling filter\n- `shove.setScalingFilter(scalingFilter)` - Set scaling filter\n- `shove.getResizeCallback()` - Get the current resize callback\n- `shove.setResizeCallback(callback)` - Register a resize callback\n- `shove.getViewportWidth()` - Get viewport width\n- `shove.getViewportHeight()` - Get viewport height\n- `shove.getViewportDimensions()` - Get viewport dimensions\n- `shove.getViewport()` - Get viewport rectangle in screen coordinates\n- `shove.handleDebugKeys()` - Display debug information\n- `shove.showDebugInfo(x, y, options)` - Display custom debug information\n\n### Layer Operations\n\n- `shove.beginLayer(name)` - Start drawing to a layer\n- `shove.endLayer()` - Finish drawing to a layer\n- `shove.createLayer(name, options)` - Create a new layer\n- `shove.removeLayer(name)` - Remove a layer\n- `shove.hasLayer(name)` - Check if a layer exists\n- `shove.getLayerOrder(name)` - Get layer drawing order\n- `shove.setLayerOrder(name, zIndex)` - Set layer drawing order\n- `shove.getLayerBlendMode(name)` - Get the blend mode and alpha mode of a layer\n- `shove.setLayerBlendMode(name, blendMode, alphaMode)` - Set blend mode and optional alpha mode\n- `shove.isLayerVisible(name)` - Check if a layer is visible\n- `shove.hideLayer(name)` - Hide a layer\n- `shove.showLayer(name)` - Show a layer\n- `shove.getLayerMask(name)` - Get the mask of a layer\n- `shove.setLayerMask(name, maskName)` - Set a layer as a mask\n- `shove.drawOnLayer(name, drawFunc)` - Draw to a layer with a callback\n- `shove.drawComposite(globalEffects, applyPersistentEffects)` - Composite and draw the current state\n\n### Effect System\n\n- `shove.addEffect(layerName, effect)` - Add an effect to a layer\n- `shove.removeEffect(layerName, effect)` - Remove an effect from a layer\n- `shove.clearEffects(layerName)` - Clear all effects from a layer\n- `shove.addGlobalEffect(effect)` - Add a global effect\n- `shove.removeGlobalEffect(effect)` - Remove a global effect\n- `shove.clearGlobalEffects()` - Clear all global effects\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foval-tutu%2Fshove","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Foval-tutu%2Fshove","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foval-tutu%2Fshove/lists"}