mirror of
https://github.com/20kdc/OC-KittenOS.git
synced 2024-11-17 07:58:06 +11:00
409 lines
11 KiB
Lua
409 lines
11 KiB
Lua
|
-- 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] = <glacier GPU function: gpu, rebound = f()>
|
||
|
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
|
||
|
}
|