-- This is released into the public domain. -- No warranty is provided, implied or otherwise. -- metamachine-vgpu.lua : Virtual GPU library -- Authors: 20kdc return { -- Creates a new virtual GPU. -- 'screens' is the component mapping, and thus: -- [id] = newGPU = function (screens) local boundScreen local backgroundRGB, foregroundRGB = 0, 0xFFFFFF local function bound() if not screens[boundScreen] then return end local gpu, rebound = screens[boundScreen]() if not gpu then screens[boundScreen] = nil return end if rebound then gpu.setBackground(backgroundRGB) gpu.setForeground(foregroundRGB) end return gpu end return { -- Virtual GPU proxy type = "gpu", -- == getAspectRatio more or less getSize = function () local gpu = bound() if not gpu then return 1, 1 else return gpu.getSize() end end, bind = function (adr, rst) boundScreen = adr local gpu = bound() if gpu then if rst then gpu.setResolution(gpu.maxResolution()) end return true end boundScreen = nil -- :( return false, "No such virtual screen" end, getScreen = function () return boundScreen end, maxResolution = function () local gpu = bound() if gpu then return gpu.maxResolution() end error("unbound") end, getResolution = function () local gpu = bound() if gpu then return gpu.getResolution() end error("unbound") end, getViewport = function () -- annoyingly undocumented so we'll pretend it's this, as OCEmu does local gpu = bound() if gpu then return gpu.getResolution() end error("unbound") end, setResolution = function (...) local gpu = bound() if gpu then return gpu.setResolution(...) end error("unbound") end, setViewport = function (...) -- annoyingly undocumented so we'll pretend it's this, as OCEmu does local gpu = bound() if gpu then return gpu.setResolution(...) end error("unbound") end, maxDepth = function () local gpu = bound() if gpu then return gpu.maxDepth() end error("unbound") end, getDepth = function () local gpu = bound() if gpu then return gpu.getDepth() end error("unbound") end, setDepth = function (...) local gpu = bound() if gpu then return gpu.setDepth(...) end error("unbound") end, get = function (...) local gpu = bound() if gpu then return gpu.get(...) end error("unbound") end, set = function (...) local gpu = bound() if gpu then return gpu.set(...) end error("unbound") end, copy = function (...) local gpu = bound() if gpu then return gpu.copy(...) end error("unbound") end, fill = function (...) local gpu = bound() if gpu then return gpu.fill(...) end error("unbound") end, getPaletteColor = function () return 0 end, setPaletteColor = function () -- Fail end, setForeground = function (rgb) checkArg(1, rgb, "number") local old = foregroundRGB foregroundRGB = rgb local gpu = bound() if gpu then gpu.setForeground(foregroundRGB) end return old end, setBackground = function (rgb) checkArg(1, rgb, "number") local old = backgroundRGB backgroundRGB = rgb local gpu = bound() if gpu then gpu.setBackground(backgroundRGB) end return old end, getForeground = function () return foregroundRGB end, getBackground = function () return backgroundRGB end } end, -- 'window' is used for span and setSize. -- emitResize(w, h) is used for screen_resized events. -- queueLine(y) is used for line queuing. newBuffer = function (window, keyboards, maxW, maxH, emitResize, queueLine) local screenW, screenH local screenText = "" local screenFR = "" local screenFG = "" local screenFB = "" local screenBR = "" local screenBG = "" local screenBB = "" -- Gets characters for R, G and B local function decodeRGB(rgb) return string.char(math.floor(rgb / 65536) % 256), string.char(math.floor(rgb / 256) % 256), string.char(rgb % 256) end -- Returns the width, or nothing if totally out of bounds. local function put(x, y, ch, fg, bg) if x < 1 or x > screenW then return end if y < 1 or y > screenH then return end local fr, fg, fb = decodeRGB(fg) local br, bg, bb = decodeRGB(bg) ch = unicode.safeTextFormat(ch) local chw = unicode.len(ch) -- Crop ch = unicode.sub(ch, 1, (screenW - x) + 1) chw = unicode.len(ch) local index = x + ((y - 1) * screenW) screenText = unicode.sub(screenText, 1, index - 1) .. ch .. unicode.sub(screenText, index + chw) screenFR = screenFR:sub(1, index - 1) .. fr:rep(chw) .. screenFR:sub(index + chw) screenFG = screenFG:sub(1, index - 1) .. fg:rep(chw) .. screenFG:sub(index + chw) screenFB = screenFB:sub(1, index - 1) .. fb:rep(chw) .. screenFB:sub(index + chw) screenBR = screenBR:sub(1, index - 1) .. br:rep(chw) .. screenBR:sub(index + chw) screenBG = screenBG:sub(1, index - 1) .. bg:rep(chw) .. screenBG:sub(index + chw) screenBB = screenBB:sub(1, index - 1) .. bb:rep(chw) .. screenBB:sub(index + chw) return chw end local function getCh(x, y) x, y = math.floor(x), math.floor(y) if x < 1 or x > screenW then return " ", 0, 0 end if y < 1 or y > screenH then return " ", 0, 0 end local index = x + ((y - 1) * screenW) local fg = (screenFR:byte(index) * 65536) + (screenFG:byte(index) * 256) + screenFB:byte(index) local bg = (screenBR:byte(index) * 65536) + (screenBG:byte(index) * 256) + screenBB:byte(index) return unicode.sub(screenText, index, index), fg, bg end -- -- Directly exposed to userspace local function setSize(w, h, first) w = math.min(math.max(math.floor(w), 1), maxW) h = math.min(math.max(math.floor(h), 1), maxH) screenW, screenH = w, h screenText = (" "):rep(w * h) screenFR = ("\xFF"):rep(w * h) screenFG = screenFR screenFB = screenFG screenBR = ("\x00"):rep(w * h) screenBG = screenBR screenBB = screenBG if not first then emitResize(w, h) end window.setSize(w, h) end local function rectOOB(x, y, w, h) if x < 1 or x > screenW - (w - 1) then return true end if y < 1 or y > screenH - (h - 1) then return true end end local function redrawLine(x, y, w) x, y, w = math.floor(x), math.floor(y), math.floor(w) w = math.min(w, screenW - (x - 1)) if w < 1 then return end if x < 1 or x > screenW then return end if y < 1 or y > screenH then return end local index = x + ((y - 1) * screenW) local currentSegmentI local currentSegment local currentSegmentR2 local function flushSegment() if not currentSegment then return end local tx = unicode.undoSafeTextFormat(currentSegment) local fg = (currentSegmentR2:byte(1) * 65536) + (currentSegmentR2:byte(2) * 256) + currentSegmentR2:byte(3) local bg = (currentSegmentR2:byte(4) * 65536) + (currentSegmentR2:byte(5) * 256) + currentSegmentR2:byte(6) -- Span format is bg, fg, not fg, bg window.span(x + currentSegmentI - 1, y, tx, bg, fg) currentSegment = nil currentSegmentI = nil currentSegmentR2 = nil end for i = 1, w do local idx = index + i - 1 local p = unicode.sub(screenText, idx, idx) local s = screenFR:sub(idx, idx) .. screenFG:sub(idx, idx) .. screenFB:sub(idx, idx) .. screenBR:sub(idx, idx) .. screenBG:sub(idx, idx) .. screenBB:sub(idx, idx) if currentSegmentR2 ~= s then flushSegment() currentSegmentI = i currentSegmentR2 = s currentSegment = p else currentSegment = currentSegment .. p end end flushSegment() end local function queueRedraw(x, y, w, h) for i = 1, h do queueLine(y + i - 1) end end setSize(maxW, maxH, true) local fgRGB, bgRGB = 0xFFFFFF, 0 local videoInterfaceChipset = { getSize = function () return 1, 1 end, maxResolution = function () return maxW, maxH end, getResolution = function () return screenW, screenH end, setResolution = setSize, maxDepth = function () return 8 end, getDepth = function () return 8 end, setDepth = function (d) end, get = getCh, set = function (x, y, str, v) x, y = math.floor(x), math.floor(y) if v then for i = 1, unicode.len(str) do put(x, y + i - 1, unicode.sub(str, i, i), fgRGB, bgRGB) end return true end local chw = put(x, y, str, fgRGB, bgRGB) if chw then queueLine(y) else return false, "Out of bounds." end return true end, copy = function (x, y, w, h, ox, oy) x, y, w, h, ox, oy = math.floor(x), math.floor(y), math.floor(w), math.floor(h), math.floor(ox), math.floor(oy) if rectOOB(x, y, w, h) then return false, "out of bounds. s" end if rectOOB(x + ox, y + oy, w, h) then return false, "out of bounds. t" end local collation = {} for iy = 1, h do collation[iy] = {} for ix = 1, w do collation[iy][ix] = {getCh(ix + x - 1, iy + y - 1)} end end for iy = 1, h do for ix = 1, w do local cc = collation[iy][ix] if ix + unicode.charWidth(cc[1]) - 1 <= w then put(ix + ox + x - 1, iy + oy + y - 1, cc[1], cc[2], cc[3]) end end end queueRedraw(x + ox, y + oy, w, h) return true end, fill = function (x, y, w, h, str) x, y, w, h = math.floor(x), math.floor(y), math.floor(w), math.floor(h) if rectOOB(x, y, w, h) then return false, "out of bounds" end str = unicode.sub(str, 1, 1) str = str:rep(math.floor(w / unicode.charWidth(str))) for i = 1, h do put(x, y + i - 1, str, fgRGB, bgRGB) end queueRedraw(x, y, w, h) return true end, setForeground = function (rgb) fgRGB = rgb end, setBackground = function (rgb) bgRGB = rgb end, } -- Various interfaces local int = { -- Internal interface line = function (y) redrawLine(1, y, screenW) end, precise = false } return function () return videoInterfaceChipset, false end, int, { type = "screen", isOn = function () return true end, turnOn = function () return true end, turnOff = function () return true end, getAspectRatio = function () return 1, 1 end, getKeyboards = function () local kbs = {} for k, v in ipairs(keyboards) do kbs[k] = v end return kbs end, setPrecise = function (p) int.precise = p end, isPrecise = function () return int.precise end, setTouchModeInverted = function (p) return false end, isTouchModeInverted = function () return false end, } end }