diff --git a/NEWS b/NEWS
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,21 @@
+1.9.4.0
+-------
+
+- Thanks to José Rafael Vieira, ansi-terminal-game now sports an API for
+  efficient dense-grid drawing.  `Cell` is exported, along with functions
+  to create Cell`s
+
+    creaCell, colorCell, rgbColorCell, paletteColorCell,
+    boldCell, reverseCell
+
+  The most important function is
+
+    cellsPlane :: Width -> Height -> [(Coords, Cell)] -> Plane
+
+  which merges the cells into a `Plane` in a single pass.
+  Many thanks to José!
+
+
 1.9.3.0
 -------
 
diff --git a/ansi-terminal-game.cabal b/ansi-terminal-game.cabal
--- a/ansi-terminal-game.cabal
+++ b/ansi-terminal-game.cabal
@@ -1,5 +1,5 @@
 name:                ansi-terminal-game
-version:             1.9.3.0
+version:             1.9.4.0
 synopsis:            cross-platform library for terminal games
 description:         Library which aims to replicate standard 2d game
                      functions (blit, ticks, timers, etc.) in a terminal
@@ -24,7 +24,7 @@
                      test/records/alone-record-left.gr,
                      test/records/balls-dims.gr,
                      test/records/balls-slow.gr
-cabal-version:       >=1.10
+cabal-version:       >= 1.10
 
 flag examples
   description:       builds examples
@@ -48,24 +48,24 @@
                        Terminal.Game.Layer.Object.Primitive,
                        Terminal.Game.Layer.Object.Record,
                        Terminal.Game.Layer.Object.Test,
-                       Terminal.Game.Utils,
                        Terminal.Game.Plane,
                        Terminal.Game.Random,
-                       Terminal.Game.Timer
+                       Terminal.Game.Timer,
+                       Terminal.Game.Utils
   build-depends:       base == 4.*,
                        ansi-terminal >= 1.0 && < 1.2,
                        array == 0.5.*,
                        bytestring >= 0.10 && < 0.13,
                        cereal == 0.5.*,
                        clock >= 0.7 && < 0.9,
-                       containers >=0.6 && <0.8,
+                       containers >= 0.6 && < 0.9,
                        exceptions == 0.10.*,
                        file-embed >= 0.0.15 && < 0.1,
                        linebreak == 1.1.*,
                        mintty == 0.1.*,
-                       mtl >=2.2 && <2.4,
-                       QuickCheck >= 2.13 && < 2.15,
-                       random >= 1.2 && < 1.3,
+                       mtl >= 2.2 && < 2.4,
+                       QuickCheck >= 2.13 && < 2.18,
+                       random >= 1.2 && < 1.4,
                        split == 0.2.*,
                        terminal-size == 0.3.*,
                        unidecode >= 0.1.0 && < 0.2,
@@ -113,14 +113,14 @@
                        bytestring >= 0.10 && < 0.13,
                        cereal == 0.5.*,
                        clock >= 0.7 && < 0.9,
-                       containers >=0.6 && <0.8,
+                       containers >= 0.6 && < 0.9,
                        exceptions == 0.10.*,
                        file-embed >= 0.0.15 && < 0.1,
                        linebreak == 1.1.*,
                        mintty == 0.1.*,
-                       mtl >=2.2 && <2.4,
-                       QuickCheck >= 2.13 && < 2.15,
-                       random >= 1.2 && < 1.3,
+                       mtl >= 2.2 && < 2.4,
+                       QuickCheck >= 2.13 && < 2.18,
+                       random >= 1.2 && < 1.4,
                        split == 0.2.*,
                        terminal-size == 0.3.*,
                        unidecode >= 0.1.0 && < 0.2,
diff --git a/src/Terminal/Game.hs b/src/Terminal/Game.hs
--- a/src/Terminal/Game.hs
+++ b/src/Terminal/Game.hs
@@ -94,6 +94,18 @@
                        planePaper,
                        planeSize,
 
+                       -- *** Performance blitting
+                       -- $performance
+
+                       Cell,
+                       creaCell,
+                       colorCell,
+                       rgbColorCell,
+                       paletteColorCell,
+                       boldCell,
+                       reverseCell,
+                       cellsPlane,
+
                        -- ** Draw
                        Draw,
                        (%), (&), (#),
@@ -215,6 +227,14 @@
 -- * If you use WASD for movement, you can readily gain compatibility with
 --   AZERTY keyboard layout by mapping “up” to both @W@ and @Z@ and “left”
 --   to @A@ and @Q@. Users from France, Belgium, and Québec will thank you.
+
+-- $performance
+-- Using 'Plane' for graphics is convenient due to the many primitives
+-- for drawing.  If you need extra performance, @ansi-terminal-game@
+-- exposes 'Cell' functions too, for efficient dense-grid drawing.
+--
+-- Remember that most likely the bottleneck for your app will still be
+-- the terminal your users run!
 
 -- | /Usable/ terminal display size (on Win32 console the last line is
 -- set aside for input). Throws 'CannotGetDisplaySize' on error.
diff --git a/src/Terminal/Game/Layer/Object/IO.hs b/src/Terminal/Game/Layer/Object/IO.hs
--- a/src/Terminal/Game/Layer/Object/IO.hs
+++ b/src/Terminal/Game/Layer/Object/IO.hs
@@ -47,7 +47,7 @@
 startIOInput :: TPS -> IO InputHandle
 startIOInput tps =
             SI.hSetBuffering SI.stdin SI.NoBuffering  >>
-            SI.hSetBuffering SI.stdout SI.NoBuffering >>
+            SI.hSetBuffering SI.stdout (SI.BlockBuffering Nothing) >>
             SI.hSetEcho SI.stdin False                >>
                 -- all the buffering settings has to happen
                 -- at the top of startIOInput. If i move
@@ -101,7 +101,11 @@
           vf d = CC.modifyMVar_ d (return . (++[e]))
 
 stopEventsIO :: [CC.ThreadId] -> IO ()
-stopEventsIO ts = mapM_ CC.killThread ts
+stopEventsIO ts = do
+        mapM_ CC.killThread ts
+        SI.hSetBuffering SI.stdout SI.NoBuffering
+        -- We need this to make `alone-playback` work, see (thnks jrvieira):
+        -- https://stackoverflow.com/questions/27324354/haskell-compiled-io-actionorder-and-flushing
 
 -----------------
 -- Game timing --
@@ -187,6 +191,20 @@
 -- ANCILLARIES --
 -----------------
 
+-- represent current terminal SGR State
+-- so we can keep track of it between blits
+data Style = Style { sBold     :: Bool
+                   , sReversed :: Bool
+                   , sColor    :: Maybe ColorInfo
+                   }
+           deriving (Eq)
+
+resetStyle :: Style
+resetStyle = Style False False Nothing
+
+cellToStyle :: Cell -> Style
+cellToStyle c = Style (isBold c) (isReversed c) (cellColor c)
+
 initPart :: IO ()
 initPart = -- check thread support
            CM.unless CC.rtsSupportsBoundThreads
@@ -232,7 +250,8 @@
                     (error "blitMap: different plane sizes")      >>
             CA.setCursorPosition 0 0                              >>
                 -- setCursorPosition is *zero* based!
-            blitToTerminal (0, 0) (orderedCells po) (orderedCells pn)
+            blitToTerminal (0, 0) (orderedCells po) (orderedCells pn) >>
+            SI.hFlush SI.stdout
 
 orderedCells :: Plane -> [[Cell]]
 orderedCells p = LS.chunksOf (fromIntegral w) cells
@@ -240,46 +259,49 @@
           cells  = map snd $ assocsPlane p
           (w, _) = planeSize p
 
-
 -- ordered sequence of cells, both old and new, like they were a String to
 -- print to screen.
 -- Coords: initial blitting position
 -- Remember that this Column is *zero* based
 blitToTerminal :: Coords -> [[Cell]] -> [[Cell]] -> IO ()
-blitToTerminal (rr, rc) ocs ncs = CM.foldM_ blitLine rr oldNew
+blitToTerminal (rr, rc) ocs ncs = CM.foldM_ blitLine (rr, resetStyle) oldNew
     where
           oldNew :: [[(Cell, Cell)]]
           oldNew = zipWith zip ocs ncs
 
-          -- row = previous row
-          blitLine :: Row -> [(Cell, Cell)] -> IO Row
-          blitLine pr ccs =
-                CM.foldM_ blitCell 0 ccs               >>
+          -- row = previous row, st = current terminal style
+          blitLine :: (Row, Style) -> [(Cell, Cell)] -> IO (Row, Style)
+          blitLine (pr, st) ccs =
+                CM.foldM blitCell (0, st) ccs    >>= \(_, st') ->
+                let wr = pr + 1 in
                 -- have to use setCursorPosition (instead of nextrow) b/c
                 -- on win there is an auto "go-to-next-line" when reaching
                 -- column end and on win it does not do so
-                let wr = pr + 1 in
                 CA.setCursorPosition (fromIntegral wr)
                                      (fromIntegral rc) >>
-                return wr
+                return (wr, st')
 
           -- k is "spaces to skip"
-          blitCell :: Int -> (Cell, Cell) -> IO Int
-          blitCell k (clo, cln)
-                | cln == clo = return (k+1)
-                | otherwise  = moveIf k         >>= \k' ->
-                               putCellStyle cln >>
-                               return k'
+          blitCell :: (Int, Style) -> (Cell, Cell) -> IO (Int, Style)
+          blitCell (k, st) (clo, cln)
+                | cln == clo = return (k+1, st)
+                | otherwise  = moveIf k >>= \k' ->
+                               putCellStyle st cln >>= \st' ->
+                               return (k', st')
 
           moveIf :: Int -> IO Int
           moveIf k | k == 0    = return k
                    | otherwise = CA.cursorForward k >>
                                  return 0
 
-putCellStyle :: Cell -> IO ()
-putCellStyle c = CA.setSGR ([CA.Reset] ++ sgrb ++ sgrr ++ sgrc) >>
-                 putChar (cellChar c)
+putCellStyle :: Style -> Cell -> IO Style
+putCellStyle st c =
+        CM.when (st /= cst) (CA.setSGR ([CA.Reset] ++ sgrb ++ sgrr ++ sgrc)) >>
+        putChar (cellChar c) >>
+        return cst
     where
+          cst = cellToStyle c
+
           sgrb | isBold c  = [CA.SetConsoleIntensity CA.BoldIntensity]
                | otherwise = []
 
diff --git a/src/Terminal/Game/Plane.hs b/src/Terminal/Game/Plane.hs
--- a/src/Terminal/Game/Plane.hs
+++ b/src/Terminal/Game/Plane.hs
@@ -113,6 +113,15 @@
 reverseCell (CellChar c b _ k) = CellChar c b True k
 reverseCell Transparent        = Transparent
 
+-- | Creates a 'Plane' from a list of individually styled cells.
+-- More efficient than folding ('&') when drawing many cells
+-- as it performs a single array update instead of one per cell.
+-- Cells outside the plane bounds are silently ignored.
+cellsPlane :: Width -> Height -> [(Coords, Cell)] -> Plane
+cellsPlane w h cells = updatePlane (blankPlane w h) (filter inside cells)
+    where
+          inside ((r, c), _) = r >= 1 && r <= h && c >= 1 && c <= w
+
 -- | Creates 'Plane' from 'String', good way to import ASCII
 -- art/diagrams. Returns a 1×1 transparent plane on empty string.
 stringPlane :: String -> Plane
