/ html_css_renderer.lua
html_css_renderer.lua
1 -- File: html_css_renderer.lua 2 3 --[[ 4 Copyright (C) 2024 chmod777 5 6 This program is free software: you can redistribute it and/or modify it under 7 the terms of the GNU Affero General Public License version 3 as published by the 8 Free Software Foundation. 9 10 This program is distributed in the hope that it will be useful, but WITHOUT ANY 11 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 12 PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 13 14 You should have received a copy of the GNU Affero General Public License along 15 with this program. If not, see <https://www.gnu.org/licenses/>. 16 ]] 17 18 function widget:GetInfo() 19 return { 20 name = 'html+css renderer', 21 desc = '', 22 author = 'chmod777', 23 date = 'May 2024', 24 license = 'GNU AGPL v3', 25 layer = -999, 26 enabled = false, 27 } 28 end 29 30 local luaWidgetDir = 'LuaUI/Widgets/' 31 32 local luaIncludeDir = luaWidgetDir..'Include/' 33 local LuaShader = VFS.Include(luaIncludeDir..'LuaShader.lua') 34 35 local myIncludesDir = luaWidgetDir..'chmod777_includes/' 36 local HTMLParser = VFS.Include(myIncludesDir..'html_css/html_parser.lua') 37 local parse_css = VFS.Include(myIncludesDir..'html_css/parse_css.lua') 38 local Layout = VFS.Include(myIncludesDir..'html_css/layout.lua') 39 local Quad,FBO = VFS.Include(myIncludesDir..'utilities_GL4.lua') 40 41 local css_source = VFS.LoadFile(myIncludesDir..'ui/main.css', VFS.RAW) 42 local html_source = VFS.LoadFile(myIncludesDir..'ui/index.html', VFS.RAW) 43 44 local vsSrc = VFS.LoadFile(myIncludesDir..'shaders/html.vs.glsl', VFS.RAW) 45 local fsSrc = VFS.LoadFile(myIncludesDir..'shaders/html.fs.glsl', VFS.RAW) 46 47 local InstanceQuad = {} 48 function InstanceQuad:new(maxElements, maxTexturesPerGroup) 49 if maxElements == nil then maxElements = 32 end 50 if maxTexturesPerGroup == nil then maxTexturesPerGroup = 8 end 51 52 local this = {} 53 this.elementCount = 0 54 this.quad = Quad:new(0, 0, 1, 1, true); 55 this.textures = {} 56 this.textureGroups = { 57 { 58 textures = {}, 59 start = 0, 60 count = 0, 61 } 62 } 63 this.maxTexturesPerGroup = maxTexturesPerGroup 64 65 local instanceVBOLayout = { 66 {id = 2, name = 'flags', size = 4, type = GL.INT}, 67 {id = 3, name = 'position_size', size = 4, type = GL.FLAOT}, 68 {id = 4, name = 'background_color', size = 4, type = GL.FLAOT}, 69 {id = 5, name = 'background_images', size = 4, type = GL.INT}, 70 {id = 6, name = 'border_widths', size = 4, type = GL.FLAOT}, 71 {id = 7, name = 'border_color', size = 4, type = GL.FLAOT}, 72 {id = 8, name = 'image0_size_offset', size = 4, type = GL.FLAOT}, 73 {id = 9, name = 'image1_size_offset', size = 4, type = GL.FLAOT}, 74 {id = 10, name = 'image2_size_offset', size = 4, type = GL.FLAOT}, 75 {id = 11, name = 'image3_size_offset', size = 4, type = GL.FLAOT}, 76 {id = 12, name = 'image0_origin_repeat_image1_origin_repeat', size = 4, type = GL.INT}, 77 {id = 13, name = 'image2_origin_repeat_image3_origin_repeat', size = 4, type = GL.INT}, 78 {id = 14, name = 'image_blend_modes', size = 4, type = GL.INT}, 79 } 80 this.instanceVBO = gl.GetVBO(GL.ARRAY_BUFFER, false) 81 this.instanceVBO:Define(maxElements, instanceVBOLayout) 82 this.quad.VAO:AttachInstanceBuffer(this.instanceVBO) 83 84 function this:addQuad(data) 85 this.instanceVBO:Upload( 86 data, 87 -1, -- attributeIndex 88 this.elementCount, -- elemOffset 89 1, -- luaStartIndex 90 #data -- luaFinishIndex 91 ) 92 this.textureGroups[#this.textureGroups].count = this.textureGroups[#this.textureGroups].count + 1 93 this.elementCount = this.elementCount+1 94 return this.elementCount 95 end 96 function this:doTexturesFitInCurrentGroup(textures) 97 local currentGroup = this.textureGroups[#this.textureGroups] 98 local indices = {} 99 local found_count = 0 100 for n,new_texture in pairs(textures) do 101 local found_in_current = false 102 for c,current_texture in pairs(currentGroup.textures) do 103 if new_texture == current_texture then 104 indices[#indices+1] = {n, c} 105 Spring.Echo() 106 found_in_current = true 107 found_count = found_count+1 108 break 109 end 110 end 111 if not found_in_current then 112 indices[#indices+1] = {n, #currentGroup.textures+n} 113 end 114 end 115 116 return (#currentGroup.textures + #textures - found_count) <= this.maxTexturesPerGroup, indices 117 end 118 function this:addTexturedQuad(data, textures) 119 local doTheyFit, indices = this:doTexturesFitInCurrentGroup(textures) 120 if doTheyFit then 121 local currentGroup = this.textureGroups[#this.textureGroups] 122 local data_index = 13 123 for i=1, #indices do 124 local n,c = unpack(indices[i]) 125 -- Spring.Echo('n', n, textures[n], 'c', c) 126 currentGroup.textures[c] = textures[n] 127 data[data_index] = c 128 data_index = data_index+1 129 end 130 this:addQuad(data) 131 else 132 currentGroup = { 133 textures = {}, 134 start = this.elementCount, 135 count = 0, 136 } 137 this.textureGroups[#this.textureGroups+1] = currentGroup 138 local data_index = 13 139 for i=1, #indices do 140 local n,c = unpack(indices[i]) 141 Spring.Echo('n', n, textures[n], 'c', c) 142 currentGroup.textures[i] = textures[i] 143 data[data_index] = i 144 end 145 this:addQuad(data) 146 end 147 end 148 function this:Delete() 149 if this.instanceVBO ~= nil then 150 this.instanceVBO:Delete() 151 end 152 if this.quad ~= nil then 153 this.quad.Delete() 154 end 155 end 156 157 return this 158 end 159 160 local stylesheet 161 local html 162 local style_tree 163 local layout_tree 164 165 local render_quads 166 local htmlShader 167 local viewGeometryUniformLoc 168 169 local viewGeometryX,viewGeometryY 170 171 local build_render_quads 172 local add_quad_recursive 173 local build_shaders 174 175 function widget:Initialize() 176 stylesheet = parse_css(css_source) 177 -- local html_source = '<div/>' 178 local html_parser = HTMLParser:new(html_source) 179 180 -- for i=1, #html_parser.lexemes do 181 -- lexeme = html_parser.lexemes[i] 182 -- Spring.Echo(lexeme.type, lexeme.source) 183 -- end 184 185 document = html_parser.parse() 186 style_tree = document:build_style_tree(stylesheet) 187 188 viewGeometryX,viewGeometryY = Spring.GetViewGeometry() 189 local size = Layout.Rect:new(0,0,viewGeometryX,viewGeometryY) 190 layout_tree = Layout.layout_tree(style_tree, Layout.Dimensions:new(size)) 191 192 build_render_quads(layout_tree) 193 194 build_shaders() 195 196 if math.bit_or then 197 Spring.Echo('bit_or found') 198 else 199 Spring.Echo('bit_or not found') 200 end 201 end 202 203 function widget:DrawScreen() 204 viewGeometryX,viewGeometryY = Spring.GetViewGeometry() 205 htmlShader:Activate() 206 gl.DepthTest(false) 207 for t,group in pairs(render_quads.textureGroups) do 208 for i=1, #group.textures do 209 gl.Texture(i-1, group.textures[i]) 210 end 211 gl.Uniform(viewGeometryUniformLoc, viewGeometryX, viewGeometryY) 212 render_quads.quad.VAO:DrawElements(GL.TRIANGLES, 6, 0, group.count, 0, group.start) 213 for i=1, #group.textures do 214 gl.Texture(i-1, false) 215 end 216 end 217 htmlShader:Deactivate() 218 end 219 220 build_render_quads = function(layout_tree) 221 render_quads = InstanceQuad:new() 222 add_quad_recursive(layout_tree) 223 end 224 add_quad_recursive = function(layout) 225 local content = layout.dimensions.content 226 local x,y,w,h = content.x, content.y, content.width, content.height 227 y = viewGeometryY-y-h 228 229 230 local blend_mode = layout.style_node:get_value_or('background-blend-mode', 0) 231 232 local background_color = layout.style_node:get_value_or('background-color', Color:new(0,0,0,0,1)) 233 local r,g,b,a = background_color.r,background_color.g,background_color.b,background_color.a 234 235 local MAX_BACKGROUND_IMAGES = 4 236 local background_image_property = layout.style_node:get_declaration('background-image') 237 local images = {} 238 if background_image_property ~= nil and background_image_property.value ~= nil then 239 local image_sources = background_image_property.value 240 for i,image_source in ipairs(image_sources) do 241 if i >= MAX_BACKGROUND_IMAGES then break end 242 images[#images+1] = image_source.value 243 local texInfo = gl.TextureInfo(image_source.value) 244 local xsize,ysize = texInfo.xsize,texInfo.ysize 245 Spring.Echo(image_source.value, xsize, ysize) 246 end 247 end 248 249 local radius = 16 250 -- border widths 251 local btop,bright,bleft,bbot = 5,5,5,5 252 -- border color 253 local br,bg,bb,ba = 1,0,0,1 254 255 256 local repeat_mode = 1 + 128 -- x: repeat, y: no-repeat 257 258 local data = { 259 0,0,0,0, -- unused flags 260 x,y,w,h, -- position size 261 r,g,b,a, -- background_color 262 0,0,0,0, -- background images indices 263 btop,bright,bleft,bbot, -- border widths 264 br,bg,bb,ba, -- border color 265 256,256,0,0, -- image0: size xy, offset xy 266 0,0,0,0, -- image1: size xy, offset xy 267 0,0,0,0, -- image2: size xy, offset xy 268 0,0,0,0, -- image3: size xy, offset xy 269 0,repeat_mode,0,0, -- image0: origin, repeat mode image1 origin, repeat mode 270 0,0,0,0, -- image2: origin, repeat mode image3 origin, repeat mode 271 0,0,0,0, -- image0-3: blendmode0, blendmode1, blendmode2, blendmode3 272 } 273 if #images > 0 then 274 render_quads:addTexturedQuad(data, images) 275 else 276 render_quads:addQuad(data) 277 end 278 279 for i,child in ipairs(layout.children) do 280 add_quad_recursive(child) 281 end 282 end 283 284 build_shaders = function() 285 htmlShader = LuaShader({ 286 vertex = vsSrc, 287 fragment = fsSrc, 288 uniformFloat = {}, 289 uniformInt = {}, 290 textures = {} 291 }, 'Quad Shader') 292 local htmlShaderCompiled = htmlShader:Initialize() 293 if not htmlShaderCompiled then 294 Spring.Echo('Quad Shader: compilation failed') 295 widgetHandler:RemoveWidget() 296 end 297 local shader = htmlShader.shaderObj 298 viewGeometryUniformLoc = gl.GetUniformLocation(shader, 'viewGeometry') 299 end