Skip to content

Instantly share code, notes, and snippets.

@Beherith
Created June 17, 2024 06:20
Show Gist options
  • Save Beherith/c965cff2a81253e37aed25f4db0c0fce to your computer and use it in GitHub Desktop.
Save Beherith/c965cff2a81253e37aed25f4db0c0fce to your computer and use it in GitHub Desktop.
LuaVBO updating cost tester
function widget:GetInfo()
return {
name = "LuaVBO Drawing Tester",
desc = "Simplest proof-of-concept VBO Drawer",
author = "Beherith GL4",
date = "2024.06.15",
license = "GNU GPL, v2 or later",
layer = 2,
enabled = true,
}
end
-- =================================================================================
-- The goal of this widget is to test the performance cost of 6 things
-- [ ] A. Cost of updating (or adding) a single instance to a VBO
-- [ ] B. Cost of updating (or adding) a single Unit to an instanceVBO
-- 17 us
-- [ ] C. Cost of uploading 1-10 elements to a VBO
-- [ ] D. Cost of uploadint 1-10 units to an instanceVBO
-- [ ] E. Cost of doing ALL uploads in one pass for single units
-- [ ] F. Cost of doing ALL uploads in one pass for a unitVBO
--
--
-- General notes:
-- - A single call costs about 13 us
-- - Adding a unitID costs 33% more (17us), and this is true across the board, even for batched uploads
-- - Each successive hot cached call costs only 40% of the first call (~5 us)
-- - The most expensive part of single calls is the upload to VBO part, account for 70% of the total time
-- - Batched uploading costs about 20% of the time of individual uploads (!)
-- - E.g if we have more than 100 items, and modify 20% of them , then batched uploads are faster than individual
--
--
--
--
--
-- =================================================================================
local testVBO = nil
local testInstanceVBO = nil
local testShader = nil
local luaShaderDir = "LuaUI/Widgets/Include/"
local LuaShader = VFS.Include(luaShaderDir.."LuaShader.lua")
VFS.Include(luaShaderDir.."instancevbotable.lua")
local rand = math.random
local isUnitVBO = true
local totalcnt = 100
local allunits = {}
local instanceCache = {}
for i=1,20 do instanceCache[i] = 0 end
if isUnitVBO then instanceCache[5] = 1 end
instanceCache[10] = 1.0
local vsSrc = [[
#version 420
#extension GL_ARB_uniform_buffer_object : require
#extension GL_ARB_shader_storage_buffer_object : require
#extension GL_ARB_shading_language_420pack: require
layout (location = 0) in vec3 position;
layout (location = 1) in vec3 normals;
layout (location = 2) in vec2 uvs;
layout (location = 3) in vec4 worldposrad;
layout (location = 4) in vec4 params; // Contains isworld shit
layout (location = 5) in vec4 color1;
layout (location = 6) in vec4 color2;
layout (location = 7) in uvec4 instData; // unitID, teamID
//__ENGINEUNIFORMBUFFERDEFS__
struct SUniformsBuffer {
uint composite; // u8 drawFlag; u8 unused1; u16 id;
uint unused2;
uint unused3;
uint unused4;
float maxHealth;
float health;
float unused5;
float unused6;
vec4 drawPos;
vec4 speed;
vec4[4] userDefined; //can't use float[16] because float in arrays occupies 4 * float space
};
layout(std140, binding=1) readonly buffer UniformsBuffer {
SUniformsBuffer uni[];
};
out DataVS {
vec4 vs_color;
};
void main() {
vec4 vertexPosition = vec4(worldposrad.xyz + position.xyz * worldposrad.w, 1.0);
if (params.x > 0.5) {
vertexPosition.xyz += uni[instData.y].drawPos.xyz;
}
vs_color = color1;
gl_Position = cameraViewProj * vec4(vertexPosition.xyz, 1.0);
}]]
local fsSrc = [[
#version 330
#extension GL_ARB_uniform_buffer_object : require
#extension GL_ARB_shading_language_420pack: require
uniform sampler2D myTexture;
//__ENGINEUNIFORMBUFFERDEFS__
in DataVS {
vec4 vs_color;
};
out vec4 fragColor;
void main() {
fragColor.rgba = vec4(vs_color.rgb, 1.0);
}]]
local function AddTestElement(instanceID, unitID, px,py,pz,r, noupload)
instanceCache[1] = px
instanceCache[2] = py
instanceCache[3] = pz
instanceCache[4] = r
pushElementInstance(testInstanceVBO, instanceCache, instanceID, true, noupload, unitID)
end
function widget:Initialize()
local engineUniformBufferDefs = LuaShader.GetEngineUniformBufferDefs()
testShader = LuaShader( {
vertex = vsSrc:gsub("//__ENGINEUNIFORMBUFFERDEFS__", engineUniformBufferDefs),
fragment = fsSrc:gsub("//__ENGINEUNIFORMBUFFERDEFS__", engineUniformBufferDefs),
uniformInt = { myTexture = 0,},
uniformFloat = { },
},
"LuaVBO Drawing Tester GL4"
)
testShader:Initialize()
local sphereVBO, numVerts, sphereIndexVBO, numIndices = makeSphereVBO(16,8,1)
local testVBOLayout = {
{id = 3, name = 'worldposrad', size = 4}, -- widthlength
{id = 4, name = 'params', size = 4}, -- emit dir
{id = 5, name = 'color1', size = 4}, --- color
{id = 6, name = 'color2', size = 4}, --- color
{id = 7, name = 'instData', type = GL.UNSIGNED_INT, size= 4},
}
if isUnitVBO then
testInstanceVBO = makeInstanceVBOTable(testVBOLayout,256, "testVBO", 7)
else
testInstanceVBO = makeInstanceVBOTable(testVBOLayout,256, "testVBO", nil)
end
if not testInstanceVBO then Spring.Echo("failed to create testInstanceVBO") end
testInstanceVBO.numVertices = numIndices
testInstanceVBO.vertexVBO = sphereVBO
testInstanceVBO.indexVBO = sphereIndexVBO
testInstanceVBO.VAO = makeVAOandAttach(testInstanceVBO.vertexVBO, testInstanceVBO.instanceVBO)
testInstanceVBO.VAO:AttachIndexBuffer(testInstanceVBO.indexVBO)
allunits = Spring.GetAllUnits()
for i= 1,totalcnt do
if isUnitVBO then
local testUnitID = allunits[math.random(1,#allunits)]
AddTestElement(i, testUnitID, 1000 * rand(), 1000* rand(), 1000*rand(), 10*rand())
else
AddTestElement(i, nil, 1000 * rand(), 1000* rand(), 1000*rand(), 10*rand())
end
end
end
local drawFrame
function widget:Update()
-- [ ] A. Cost of updating (or adding) a single instance to a VBO
-- [ ] B. Cost of updating (or adding) a single Unit to an instanceVBO
-- [ ] C. Cost of uploading 1-10 elements to a VBO
-- [ ] D. Cost of uploadint 1-10 units to an instanceVBO
-- [ ] E. Cost of doing ALL uploads in one pass for single units
-- [ ] F. Cost of doing ALL uploads in one pass for a unitVBO
drawFrame = Spring.GetDrawFrame()
if isUnitVBO or true then
if drawFrame%4 ==0 then
-- [ ] B. Cost of updating (or adding) a single Unit to an instanceVBO
-- Costs on average 16us per call
local testUnitID = allunits[math.random(1,#allunits)]
tracy.ZoneBeginN("testVBO:".. (isUnitVBO and "B" or "A"))
AddTestElement(math.random(1,totalcnt-1) , isUnitVBO and testUnitID or nil, 1000 * rand(), 1000* rand(), 1000*rand(), 10*rand())
tracy.ZoneEnd()
elseif drawFrame%4 ==1 then
-- [ ] D. Cost of uploading 1-10 units to an instanceVBO
-- Costs on average 16 us for first, + 6 us per additional unit
local count = math.floor(drawFrame/4) % 20
local testUnitID = allunits[math.random(1,#allunits)]
tracy.ZoneBeginN(string.format("testVBO:%s:%03d",isUnitVBO and "D" or "C" , count))
for j =1, count do
AddTestElement(math.random(1,totalcnt-1) , isUnitVBO and testUnitID or nil, 1000 * rand(), 1000* rand(), 1000*rand(), 10*rand())
end
tracy.ZoneEnd()
elseif drawFrame %4 == 2 then
-- [ ] F. Cost of uploading 1-10 DIFFERENT units to an instanceVBO
-- Costs on average 16 us for first, + 6 us per additional unit
local count = math.floor(drawFrame/4) % 20
tracy.ZoneBeginN(string.format("testVBO:%s:%03d",isUnitVBO and "F" or "E" , count))
for j =1, count do
local testUnitID = allunits[math.random(1,#allunits)]
AddTestElement(math.random(1,totalcnt-1) , isUnitVBO and testUnitID or nil, 1000 * rand(), 1000* rand(), 1000*rand(), 10*rand())
end
tracy.ZoneEnd()
else
-- [ ] H. Cost of NOT uploading 1-10 DIFFERENT units to an instanceVBO, then uploading all!
-- Costs on average 16 us for first, + 6 us per additional unit
local count = math.floor(drawFrame/4) % 20
tracy.ZoneBeginN(string.format("testVBO:%s:%03d",isUnitVBO and "H" or "G" , count))
for j =1, count do
local testUnitID = allunits[math.random(1,#allunits)]
AddTestElement(math.random(1,totalcnt-1) , isUnitVBO and testUnitID or nil, 1000 * rand(), 1000* rand(), 1000*rand(), 10*rand(), true) -- note NOUPLOAD
end
tracy.ZoneEnd()
tracy.ZoneBeginN(string.format("testVBO:%s:%04d", isUnitVBO and "H" or "G", totalcnt))
uploadAllElements(testInstanceVBO)
tracy.ZoneEnd()
end
else
end
end
function widget:DrawWorld()
if testInstanceVBO.usedElements > 0 then
gl.Culling(false)
testShader:Activate()
drawInstanceVBO(testInstanceVBO)
testShader:Deactivate()
end
end
function widget:Shutdown()
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment