digistuff/gpu.lua
cheapie a5ca8f19b7 Add "sendregion" command to GPU
This allows part of a buffer to be sent instead of the whole thing, meaning that driving digiscreen arrays can now be done with fewer commands and no temporary buffers.
This also greatly improves the speed of doing so, and I suppose also allows hardware-accelerated scrolling if your display is smaller than 64x64...
2021-02-21 14:45:08 -06:00

459 lines
16 KiB
Lua

local font = dofile(minetest.get_modpath("digistuff")..DIR_DELIM.."gpu-font.lua")
local function explodebits(input)
local output = {}
for i=0,7,1 do
output[i] = input%(2^(i+1)) >= 2^i
end
return output
end
local function implodebits(input)
local output = 0
for i=0,7,1 do
output = output + (input[i] and 2^i or 0)
end
return output
end
local function rgbtohsv(r,g,b)
r = r/255
g = g/255
b = b/255
max = math.max(r,g,b)
min = math.min(r,g,b)
delta = max-min
local hue = 0
if delta > 0 then
if max == r then
hue = (g-b)/delta
hue = (hue%6)*60
elseif max == g then
hue = (b-r)/delta
hue = 60*(hue+2)
elseif max == b then
hue = (r-g)/delta
hue = 60*(hue+4)
end
hue = hue/360
end
local sat = 0
if max > 0 then
sat = delta/max
end
return math.floor(hue*255),math.floor(sat*255),math.floor(max*255)
end
local function hsvtorgb(h,s,v)
h = h/255*360
s = s/255
v = v/255
local c = s*v
local x = (h/60)%2
x = 1-math.abs(x-1)
x = x*c
local m = v-c
local r = 0
local g = 0
local b = 0
if h < 60 then
r = c
g = x
elseif h < 120 then
r = x
g = c
elseif h < 180 then
g = c
b = x
elseif h < 240 then
g = x
b = c
elseif h < 300 then
r = x
b = c
else
r = c
b = x
end
r = r+m
g = g+m
b = b+m
return math.floor(r*255),math.floor(g*255),math.floor(b*255)
end
local function bitwiseblend(srcr,dstr,srcg,dstg,srcb,dstb,mode)
local srbits = explodebits(srcr)
local sgbits = explodebits(srcg)
local sbbits = explodebits(srcb)
local drbits = explodebits(dstr)
local dgbits = explodebits(dstg)
local dbbits = explodebits(dstb)
for i=0,7,1 do
if mode == "and" then
drbits[i] = srbits[i] and drbits[i]
dgbits[i] = sgbits[i] and dgbits[i]
dbbits[i] = sbbits[i] and dbbits[i]
elseif mode == "or" then
drbits[i] = srbits[i] or drbits[i]
dgbits[i] = sgbits[i] or dgbits[i]
dbbits[i] = sbbits[i] or dbbits[i]
elseif mode == "xor" then
drbits[i] = srbits[i] ~= drbits[i]
dgbits[i] = sgbits[i] ~= dgbits[i]
dbbits[i] = sbbits[i] ~= dbbits[i]
elseif mode == "xnor" then
drbits[i] = srbits[i] == drbits[i]
dgbits[i] = sgbits[i] == dgbits[i]
dbbits[i] = sbbits[i] == dbbits[i]
elseif mode == "not" then
drbits[i] = not srbits[i]
dgbits[i] = not sgbits[i]
dbbits[i] = not sbbits[i]
elseif mode == "nand" then
drbits[i] = not (srbits[i] and drbits[i])
dgbits[i] = not (sgbits[i] and dgbits[i])
dbbits[i] = not (sbbits[i] and dbbits[i])
elseif mode == "nor" then
drbits[i] = not (srbits[i] or drbits[i])
dgbits[i] = not (sgbits[i] or dgbits[i])
dbbits[i] = not (sbbits[i] or dbbits[i])
end
end
return string.format("%02X%02X%02X",implodebits(drbits),implodebits(dgbits),implodebits(dbbits))
end
local function blend(src,dst,mode,transparent)
local srcr = tonumber(string.sub(src,1,2),16)
local srcg = tonumber(string.sub(src,3,4),16)
local srcb = tonumber(string.sub(src,5,6),16)
local dstr = tonumber(string.sub(dst,1,2),16)
local dstg = tonumber(string.sub(dst,3,4),16)
local dstb = tonumber(string.sub(dst,5,6),16)
local op = "normal"
if type(mode) == "string" then op = string.lower(mode) end
if op == "normal" then
return src
elseif op == "nop" then
return dst
elseif op == "overlay" then
return (string.upper(src) == string.upper(transparent)) and dst or src
elseif op == "add" then
local r = math.min(255,srcr+dstr)
local g = math.min(255,srcg+dstg)
local b = math.min(255,srcb+dstb)
return string.format("%02X%02X%02X",r,g,b)
elseif op == "sub" then
local r = math.max(0,dstr-srcr)
local g = math.max(0,dstg-srcg)
local b = math.max(0,dstb-srcb)
return string.format("%02X%02X%02X",r,g,b)
elseif op == "isub" then
local r = math.max(0,srcr-dstr)
local g = math.max(0,srcg-dstg)
local b = math.max(0,srcb-dstb)
return string.format("%02X%02X%02X",r,g,b)
elseif op == "average" then
local r = math.min(255,(srcr+dstr)/2)
local g = math.min(255,(srcg+dstg)/2)
local b = math.min(255,(srcb+dstb)/2)
return string.format("%02X%02X%02X",r,g,b)
elseif op == "and" or op == "or" or op == "xor" or op == "xnor" or op == "not" or op == "nand" or op == "nor" then
return bitwiseblend(srcr,dstr,srcg,dstg,srcb,dstb,op)
elseif op == "tohsv" or op == "rgbtohsv" then
return string.format("%02X%02X%02X",rgbtohsv(srcr,srcg,srcb))
elseif op == "torgb" or op == "hsvtorgb" then
return string.format("%02X%02X%02X",hsvtorgb(srcr,srcg,srcb))
else
return src
end
end
local function runcommand(pos,meta,command)
if type(command) ~= "table" then return end
if command.command == "createbuffer" then
if type(command.buffer) ~= "number" or type(command.xsize) ~= "number" or type(command.ysize) ~= "number" then return end
local bufnum = math.floor(command.buffer)
if bufnum < 0 or bufnum > 7 then return end
local xsize = math.min(64,math.floor(command.xsize))
local ysize = math.min(64,math.floor(command.ysize))
if xsize < 1 or ysize < 1 then return end
local fillcolor = command.fill
if type(fillcolor) ~= "string" or string.len(fillcolor) > 7 or string.len(fillcolor) < 6 then fillcolor = "000000" end
if string.sub(fillcolor,1,1) == "#" then fillcolor = string.sub(fillcolor,2,7) end
if not tonumber(fillcolor,16) then fillcolor = "000000" end
local buffer = {}
buffer.xsize = xsize
buffer.ysize = ysize
for y=1,ysize,1 do
buffer[y] = {}
for x=1,xsize,1 do
buffer[y][x] = fillcolor
end
end
meta:set_string("buffer"..bufnum,minetest.serialize(buffer))
elseif command.command == "send" then
if type(command.buffer) ~= "number" or type(command.channel) ~= "string" then return end
local bufnum = math.floor(command.buffer)
if bufnum < 0 or bufnum > 7 then return end
local buffer = meta:get_string("buffer"..bufnum)
if string.len(buffer) == 0 then return end
buffer = minetest.deserialize(buffer)
if type(buffer) == "table" then
digiline:receptor_send(pos,digiline.rules.default,command.channel,buffer)
end
elseif command.command == "sendregion" then
if type(command.buffer) ~= "number" or type(command.channel) ~= "string" then return end
local bufnum = math.floor(command.buffer)
if bufnum < 0 or bufnum > 7 then return end
local buffer = meta:get_string("buffer"..bufnum)
if string.len(buffer) == 0 then return end
buffer = minetest.deserialize(buffer)
if type(buffer) ~= "table" then return end
if type(command.x1) ~= "number" or type(command.x2) ~= "number" or type(command.y1) ~= "number" or type(command.x2) ~= "number" then return end
local x1 = math.min(64,math.floor(command.x1))
local y1 = math.min(64,math.floor(command.y1))
local x2 = math.min(64,math.floor(command.x2))
local y2 = math.min(64,math.floor(command.y2))
if x1 < 1 or y1 < 1 or x2 < 1 or y2 < 1 then return end
x2 = math.min(x2,buffer.xsize)
y2 = math.min(y2,buffer.ysize)
if x1 > x2 or y1 > y2 then return end
local tempbuf = {}
for y=y1,y2,1 do
local dsty = y-y1+1
tempbuf[dsty] = {}
for x=x1,x2,1 do
local dstx = x-x1+1
tempbuf[dsty][dstx] = buffer[y][x]
end
end
digiline:receptor_send(pos,digiline.rules.default,command.channel,tempbuf)
elseif command.command == "drawrect" then
if type(command.buffer) ~= "number" or type(command.x1) ~= "number" or type(command.y1) ~= "number" or type(command.x2) ~= "number" or type(command.y2) ~= "number" then return end
local bufnum = math.floor(command.buffer)
if bufnum < 0 or bufnum > 7 then return end
local x1 = math.min(64,math.floor(command.x1))
local y1 = math.min(64,math.floor(command.y1))
local x2 = math.min(64,math.floor(command.x2))
local y2 = math.min(64,math.floor(command.y2))
if x1 < 1 or y1 < 1 or x2 < 1 or y2 < 1 then return end
local buffer = meta:get_string("buffer"..bufnum)
if string.len(buffer) == 0 then return end
buffer = minetest.deserialize(buffer)
if type(buffer) ~= "table" then return end
x2 = math.min(x2,buffer.xsize)
y2 = math.min(y2,buffer.ysize)
if x1 > x2 or y1 > y2 then return end
local fillcolor = command.fill
if type(fillcolor) ~= "string" or string.len(fillcolor) > 7 or string.len(fillcolor) < 6 then fillcolor = "000000" end
if string.sub(fillcolor,1,1) == "#" then fillcolor = string.sub(fillcolor,2,7) end
if not tonumber(fillcolor,16) then fillcolor = "000000" end
local edgecolor = command.edge
if type(edgecolor) ~= "string" or string.len(edgecolor) > 7 or string.len(edgecolor) < 6 then edgecolor = fillcolor end
if string.sub(edgecolor,1,1) == "#" then edgecolor = string.sub(edgecolor,2,7) end
if not tonumber(edgecolor,16) then edgecolor = fillcolor end
for y=y1,y2,1 do
for x=x1,x2,1 do
buffer[y][x] = fillcolor
end
end
if fillcolor ~= edgecolor then
for x=x1,x2,1 do
buffer[y1][x] = edgecolor
buffer[y2][x] = edgecolor
end
for y=y1,y2,1 do
buffer[y][x1] = edgecolor
buffer[y][x2] = edgecolor
end
end
meta:set_string("buffer"..bufnum,minetest.serialize(buffer))
elseif command.command == "drawpoint" then
if type(command.buffer) ~= "number" or type(command.x) ~= "number" or type(command.y) ~= "number" then return end
local bufnum = math.floor(command.buffer)
if bufnum < 0 or bufnum > 7 then return end
local x = math.floor(command.x)
local y = math.floor(command.y)
if x < 1 or y < 1 then return end
local buffer = meta:get_string("buffer"..bufnum)
if string.len(buffer) == 0 then return end
buffer = minetest.deserialize(buffer)
if type(buffer) ~= "table" then return end
if x > buffer.xsize or y > buffer.ysize then return end
local color = command.color
if type(color) ~= "string" or string.len(color) > 7 or string.len(color) < 6 then color = "000000" end
if string.sub(color,1,1) == "#" then color = string.sub(color,2,7) end
if not tonumber(color,16) then color = "000000" end
buffer[y][x] = color
meta:set_string("buffer"..bufnum,minetest.serialize(buffer))
elseif command.command == "copy" then
if type(command.src) ~= "number" or type(command.dst) ~= "number" or type(command.srcx) ~= "number" or type(command.srcy) ~= "number" or type(command.dstx) ~= "number" or type(command.dsty) ~= "number" or type(command.xsize) ~= "number" or type(command.ysize) ~= "number" then return end
local src = math.floor(command.src)
if src < 0 or src > 7 then return end
local dst = math.floor(command.dst)
if dst < 0 or dst > 7 then return end
local srcx = math.floor(command.srcx)
local srcy = math.floor(command.srcy)
local dstx = math.floor(command.dstx)
local dsty = math.floor(command.dsty)
local xsize = math.floor(command.xsize)
local ysize = math.floor(command.ysize)
if srcx < 1 or srcy < 1 or dstx < 1 or dsty < 1 or xsize < 1 or ysize < 1 then return end
local sourcebuffer = meta:get_string("buffer"..src)
local destbuffer = meta:get_string("buffer"..dst)
if string.len(sourcebuffer) == 0 then return end
sourcebuffer = minetest.deserialize(sourcebuffer)
if type(sourcebuffer) ~= "table" then return end
if string.len(destbuffer) == 0 then return end
destbuffer = minetest.deserialize(destbuffer)
if type(destbuffer) ~= "table" then return end
if srcx + xsize-1 > sourcebuffer.xsize or srcy + ysize-1 > sourcebuffer.ysize then return end
if dstx + xsize-1 > destbuffer.xsize or dsty + ysize-1 > destbuffer.ysize then return end
local transparent = command.transparent
if type(transparent) ~= "string" or string.len(transparent) > 7 or string.len(transparent) < 6 then transparent = "000000" end
if string.sub(transparent,1,1) == "#" then transparent = string.sub(transparent,2,7) end
if not tonumber(transparent,16) then transparent = "000000" end
for y=0,ysize-1,1 do
for x=0,xsize-1,1 do
local srcpx = sourcebuffer[srcy+y][srcx+x]
local destpx = destbuffer[dsty+y][dstx+x]
destbuffer[dsty+y][dstx+x] = blend(srcpx,destpx,command.mode,transparent)
end
end
meta:set_string("buffer"..dst,minetest.serialize(destbuffer))
elseif command.command == "load" then
if type(command.buffer) ~= "number" or type(command.x) ~= "number" or type(command.y) ~= "number" or type(command.data) ~= "table" then return end
local bufnum = math.floor(command.buffer)
if bufnum < 0 or bufnum > 7 then return end
local xstart = math.floor(command.x)
local ystart = math.floor(command.y)
if xstart < 1 or ystart < 1 then return end
local buffer = meta:get_string("buffer"..bufnum)
if string.len(buffer) == 0 then return end
buffer = minetest.deserialize(buffer)
if type(buffer) ~= "table" then return end
if type(command.data[1]) ~= "table" then return end
if #command.data[1] < 1 then return end
local ysize = #command.data
local xsize = #command.data[1]
if xstart+xsize-1 > buffer.xsize or ystart+ysize-1 > buffer.ysize then return end
for y=1,ysize,1 do
if type(command.data[y]) == "table" then
for x=1,xsize,1 do
local color = command.data[y][x]
if type(color) == "string" then
if string.len(color) == 7 then color = string.sub(color,2,7) end
if tonumber(color,16) then
buffer[ystart+y-1][xstart+x-1] = color
end
end
end
end
end
meta:set_string("buffer"..bufnum,minetest.serialize(buffer))
elseif command.command == "text" then
if type(command.buffer) ~= "number" or type(command.x) ~= "number" or type(command.y) ~= "number" or type(command.text) ~= "string" or string.len(command.text) < 1 then return end
command.text = string.sub(command.text,1,16)
local bufnum = math.floor(command.buffer)
if bufnum < 0 or bufnum > 7 then return end
local x = math.floor(command.x)
local y = math.floor(command.y)
if x < 1 or y < 1 then return end
local buffer = meta:get_string("buffer"..bufnum)
if string.len(buffer) == 0 then return end
buffer = minetest.deserialize(buffer)
if type(buffer) ~= "table" then return end
if x > buffer.xsize or y > buffer.ysize then return end
local color = command.color
if type(color) ~= "string" or string.len(color) > 7 or string.len(color) < 6 then color = "ff6600" end
if string.sub(color,1,1) == "#" then color = string.sub(color,2,7) end
if not tonumber(color,16) then color = "ff6600" end
for i=1,string.len(command.text),1 do
local char = font[string.byte(string.sub(command.text,i,i))]
for chary=1,12,1 do
for charx=1,5,1 do
local startx = x + (i*6-6)
if char[chary][charx] and y+chary-1 <= buffer.ysize and startx+charx-1 <= buffer.xsize then
local dstpx = buffer[y+chary-1][startx+charx-1]
buffer[y+chary-1][startx+charx-1] = blend(color,dstpx,command.mode,"")
end
end
end
end
meta:set_string("buffer"..bufnum,minetest.serialize(buffer))
end
end
minetest.register_node("digistuff:gpu", {
description = "Digilines 2D Graphics Processor",
groups = {cracky=3},
on_construct = function(pos)
local meta = minetest.get_meta(pos)
meta:set_string("formspec","field[channel;Channel;${channel}")
end,
tiles = {
"digistuff_gpu_top.png",
"jeija_microcontroller_bottom.png",
"jeija_microcontroller_sides.png",
"jeija_microcontroller_sides.png",
"jeija_microcontroller_sides.png",
"jeija_microcontroller_sides.png"
},
inventory_image = "digistuff_gpu_top.png",
drawtype = "nodebox",
selection_box = {
--From luacontroller
type = "fixed",
fixed = { -8/16, -8/16, -8/16, 8/16, -5/16, 8/16 },
},
_digistuff_channelcopier_fieldname = "channel",
node_box = {
--From Luacontroller
type = "fixed",
fixed = {
{-8/16, -8/16, -8/16, 8/16, -7/16, 8/16}, -- Bottom slab
{-5/16, -7/16, -5/16, 5/16, -6/16, 5/16}, -- Circuit board
{-3/16, -6/16, -3/16, 3/16, -5/16, 3/16}, -- IC
}
},
paramtype = "light",
sunlight_propagates = true,
on_receive_fields = function(pos, formname, fields, sender)
local name = sender:get_player_name()
if minetest.is_protected(pos,name) and not minetest.check_player_privs(name,{protection_bypass=true}) then
minetest.record_protection_violation(pos,name)
return
end
local meta = minetest.get_meta(pos)
if fields.channel then meta:set_string("channel",fields.channel) end
end,
digiline =
{
receptor = {},
effector = {
action = function(pos,node,channel,msg)
local meta = minetest.get_meta(pos)
if meta:get_string("channel") ~= channel or type(msg) ~= "table" then return end
if type(msg[1]) == "table" then
for i=1,32,1 do
if type(msg[i]) == "table" then
runcommand(pos,meta,msg[i])
end
end
else
runcommand(pos,meta,msg)
end
end
},
},
})
minetest.register_craft({
output = "digistuff:gpu",
recipe = {
{"","default:steel_ingot",""},
{"digilines:wire_std_00000000","mesecons_luacontroller:luacontroller0000","digilines:wire_std_00000000"},
{"dye:red","dye:green","dye:blue"},
}
})