OKay, so in this session, I want to turn my previous simple triangle display into a 3D cube. To achieve that I would like to start using an index buffer, and also introduce the minimal support required to handle an ortho projection, so that I could play a simple cube animation and see it rotating. This all sounds pretty easy said like this, but I'm sure I will find some trouble on my path, as usual 😆. So let's get started.
-- Prepare our vertex buffer here, with support to write to it: -- We need 8 vertices, and for each vertex, a vec3 position (3 floats) -- as well as an RGBA colors (4 bytes or 1 float) -- So 4 floats per vertex local vbufIdx = self.vkeng:create_vertex_buffer(4 * 4 * 8, nvk.BufferCreateFlags.SEQUENTIAL_HOST_WRITE) local vbuf = self.vkeng:get_vertex_buffer(vbufIdx) -- Specify the vertex attributes: -- This time we build a cube here so we need 8 vertices. vbuf:add_rgb32f(0) -- vec3 position at location=0 vbuf:add_rgba8(1) -- vec4 color at location=2 -- write the data: -- Cube half-size: local hs = 10.0 vbuf:write_float_array { -- front plane (with positive z values) -hs, -hs, hs, nv.rgba_to_f32(255, 0, 0, 255), -hs, hs, hs, nv.rgba_to_f32(255, 0, 127, 255), hs, hs, hs, nv.rgba_to_f32(255, 0, 255, 255), hs, -hs, hs, nv.rgba_to_f32(255, 127, 0, 255), -- back plane (with negative z values) -hs, -hs, -hs, nv.rgba_to_f32(0, 0, 0, 255), -hs, hs, -hs, nv.rgba_to_f32(0, 0, 127, 255), hs, hs, -hs, nv.rgba_to_f32(0, 0, 255, 255), hs, -hs, -hs, nv.rgba_to_f32(0, 127, 0, 255), }
-- Next we need the index buffer data: local indices = { -- front face, counter-clockwise front face: 0, 3, 1, 1, 3, 2, -- back face: 4, 7, 5, 5, 7, 6, -- left face: 4, 0, 5, 5, 0, 1, -- right face: 3, 7, 2, 2, 7, 6, -- top face: 0, 4, 3, 4, 7, 3, -- bottom face: 1, 2, 5, 5, 2, 6, }
local num_indices = #indices local ibufIdx = self.vkeng:create_index_buffer(4 * num_indices, nvk.BufferCreateFlags.SEQUENTIAL_HOST_WRITE) local ibuf = self.vkeng:get_index_buffer(ibufIdx) -- Load the indices: ibuf:write_u32_array(indices)
void VulkanCommandBuffer::bind_index_buffer(VulkanIndexBuffer* ibuf, U64 startOffset) { ASSERT(is_recording()); VkBuffer buf = ibuf->get_buffer()->getVk(); // Use the absolute start point of this index buffer as a starting point: U64 offset = ibuf->get_start_offset() + startOffset; // Only using UIN32 indices for now: _device->vkCmdBindIndexBuffer(_buffer, buf, offset, VK_INDEX_TYPE_UINT32); }
void SimpleCmdBuffersProvider::get_buffers(FrameDesc& fdesc, VkCommandBufferList& buffers) { // Write the command buffer: U32 idx = fdesc.swapchainImageIndex; // Re-record the command buffer as above: auto* cbuf = _cbufs[idx].get(); auto* fbuf = _renderer->get_swapchain_framebuffer(idx); // We update our push constants here to contain a time value: F32 time = (F32)fdesc.frameTime; // logDEBUG("Writing time value: {}", time); // We write the time as the z element of the first vec4: _pushArr.write_f32(time, 8); // Push constants stages : U32 pstages = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT; U32 width = _renderer->get_swapchain_width(); U32 height = _renderer->get_swapchain_height(); // Check if we need to rebuild the pipeline: if (_width != width || _height != height) { _cfg->getCurrentViewportState()->setViewport((float)width, (float)height); _pipeline = _renderer->get_device()->create_graphics_pipeline( _cfg->getVk(), _pipelineCache->getVk()); _width = width; _height = height; } fbuf->set_clear_color(0, 0.2, 0.2, 0.2, 1.0); cbuf->begin(0); // Begin rendering into the swapchain framebuffer: cbuf->begin_inline_pass(_rpass.get(), fbuf); // Bind the graphics pipeline: cbuf->push_bind_graphics_pipeline(_pipeline->getVk()); // Bind the vertex buffer: cbuf->bind_vertex_buffer(_vbuf.get(), 0); // add the push constants cbuf->write_push_contants(_playout->getVk(), pstages, 0, _pushArr.get_size(), _pushArr.get_data()); // Draw our triangle: cbuf->draw(3); // End the render pass cbuf->end_render_pass(); // Finish the command buffer: cbuf->finish(); // Add the buffer to the list: buffers.push_back(cbuf->getVk()); }
namespace nvk { PushConstants::PushConstants(U32 size, VulkanPipelineLayout* playout, U32 pstages, U32 index) : RenderNode(index), _playout(playout), _pstages(pstages), _byteArray(size) { logDEBUG("Creating PushConstants object."); } PushConstants::~PushConstants() { logDEBUG("Deleting PushConstants object."); } void PushConstants::record_commands(RenderGraphContext& ctx) { auto* cbuf = ctx.cbuf; cbuf->write_push_contants(_playout->getVk(), _pstages, _offset, _byteArray.get_size(), _byteArray.get_data()); } } // namespace nvk/
struct GraphUpdater : public nv::RefObject { virtual void update(FrameDesc&) = 0; };
void RenderPass::record_commands(RenderGraphContext& ctx) { auto* cbuf = ctx.graph->get_cmd_buffer(_cmdBufferIndex); // auto* fbuf = ctx.graph->get_framebuffer(_framebufferIndex); // Retrieve the framebuffer: VulkanFramebuffer* fbuf = _framebuffer.get(); if (fbuf == nullptr) { // Use the current swapchain framebuffer: fbuf = ctx.get_current_framebuffer(); } // .. More stuff here. }
2022-12-11 13:06:30.196828 [FATAL] No lua converted available for VkPhysicalDevice stack traceback: [string "setup.lua"]:204: in function 'THROW' [string "setup.lua"]:199: in function 'CHECK' [string "reflection.lua.VectorConverter"]:68: in function 'writeGetter' [string "bind.FunctionWriter"]:261: in function 'writeFunctionOverload' [string "bind.FunctionWriter"]:198: in function 'writeFunctionBinds' [string "bind.FunctionWriter"]:62: in function 'writeFunctionBindings' [string "bind.ClassWriter"]:223: in function 'writeContent' [string "bind.ClassWriter"]:15: in function '__init' [string "setup.lua"]:161: in function 'classWriter' [string "app.NervBind"]:332: in function 'func' [string "reflection.Scope"]:33: in function 'func' ... [string "bind.LunaManager"]:164: in function 'foreachClass' [string "app.NervBind"]:314: in function 'writeBindings' [string "app.NervBind"]:217: in function 'processModuleConfig' [string "app.NervBind"]:264: in function 'run' [string "app.AppBase"]:24: in function '__init' [string "app.NervBind"]:12: in function '__init' [string "setup.lua"]:161: in function 'app' [string "run.lua"]:70: in function <[string "run.lua"]:68> [C]: in function 'xpcall' [string "run.lua"]:73: in main chunk
-- Build the rendergraph here local rgraph = nvk.RenderGraph() -- Number of swapchain images: local num = self.renderer:get_num_swapchain_images() local cbufs = rgraph:add_primary_cmd_buffer_selector(num, 0) -- Add a render pass to that cmd buffer selector: local rpass = cbufs:add_render_pass(renderpass) -- Add the pipeline: local pline = rpass:add_graphics_pipeline(cfg, pipelineCache) local pushc = pline:add_push_constants(32, pipelineLayout) local parr = pushc:get_bytearray() parr:write_vec4f(nv.Vec4f(0.5, 0.5, 0.0, 0.0)) parr:write_vec4f(nv.Vec4f(0.0, 0.0, 0.4, 0.4)) self:add_triangle(pline) self.renderer:add_rendergraph(rgraph)
namespace nvk { struct TimePushConstantsUpdater : public GraphUpdater { nv::RefPtr<PushConstants> pushc{nullptr}; U32 offset{0}; TimePushConstantsUpdater(PushConstants* pushc, U32 offset) : pushc(pushc), offset(offset){}; void update(FrameDesc& fdesc) override; }; void TimePushConstantsUpdater::update(FrameDesc& fdesc) { auto& barr = pushc->get_bytearray(); // We update our push constants here to contain a time value: F32 time = (F32)fdesc.frameTime; // We write the time as the z element of the first vec4: barr.write_f32(time, 8); } auto create_time_push_constant_updater(PushConstants* pushc, U32 offset) -> GraphUpdater* { auto updater = nv::create_ref_object<TimePushConstantsUpdater>(pushc, offset); return updater.release(); } }
self.renderer:add_rendergraph(rgraph) -- Apply updater for the push constants: local updater = nvk.create_time_push_constant_updater(pushc, 8) rgraph:add_updater(updater)
namespace nvk { auto create_simple_cmd_buf_provider( VulkanRenderer* renderer, VulkanRenderPass* rpass, VulkanVertexBuffer* vbuf, VulkanPipelineLayout* playout, VulkanGraphicsPipelineCreateInfo* cfg, VulkanPipelineCache* pcache, const VulkanCommandBufferList& cbufs, const nv::ByteArray& pushArr) -> CmdBuffersProvider*; auto create_time_push_constant_updater(PushConstants* pushc, U32 offset) -> GraphUpdater*; } // namespace nvk/
---@meta nvk = nvk or {}
function Class:writeFunctionDefs(funcs) for _, f in ipairs(funcs) do local ns = self:getNamespaceStack(f) local ns_str = table.concat(ns, ".") if not f:isIgnored() and not f:isConstructor() and not f:isDestructor() then local sigs = f:getPublicSignatures(true) local func_line = "" for idx, sig in ipairs(sigs) do -- get the list of arguments: local args = sig:getArguments() local anames = {} -- if idx == 1 then for _, arg in ipairs(args) do -- Ignore the canonical lua state: if not arg:isCanonicalLuaState() then local aname = arg:getName() local opt = arg:hasDefault() and "?" or "" table.insert(anames, aname) local atype = arg:getType():getLuaTypeName() if atype == "luna.LuaFunction" then atype = "function" end -- Write the parameter desc line: self:writeSubLine("---@param ${1}${3} ${2}", aname, atype, opt) end end -- get the return type: local rtype = sig:getReturnType() if rtype ~= nil then local rtname = rtype:getLuaTypeName() -- We don't write void return if rtname ~= "" then self:writeSubLine("---@return ${1}", rtname) end end local fname = f:getLuaName(sig) -- Then we write the actual function proto: local anames = table.concat(anames, ", ") -- This is the version version of the function so we should write the function line for it: func_line = table.concat({ "function ", ns_str, ".", fname, "(", anames, ") end" }, "") self:writeLine(func_line) self:newLine() end end end end
---@meta nvk = nvk or {} ---@param renderer nvk.VulkanRenderer ---@param rpass nvk.VulkanRenderPass ---@param vbuf nvk.VulkanVertexBuffer ---@param playout nvk.VulkanPipelineLayout ---@param cfg nvk.VulkanGraphicsPipelineCreateInfo ---@param pcache nvk.VulkanPipelineCache ---@param cbufs nvk.VulkanCommandBufferList ---@param pushArr nv.ByteArray ---@return nvk.CmdBuffersProvider function nvk.create_simple_cmd_buf_provider(renderer, rpass, vbuf, playout, cfg, pcache, cbufs, pushArr) end ---@param pushc nvk.PushConstants ---@param offset integer ---@return nvk.GraphUpdater function nvk.create_time_push_constant_updater(pushc, offset) end
void write_vec2f(const Vec2f& value, U64 idx = U64_MAX);
namespace nv { using Vec2f = Vec2<float>; using Vec2d = Vec2<double>; }
namespace nv { template <typename T, typename U> struct MyStruct { auto do_something(const U& arg0)-> T*; }; }
${T}@nv::MyStruct
<- ${T}*@nv::MyStruct
and ${U}@nv::MyStruct
<- const ${U}@nv::MyStruct
<- const ${U}&@nv::MyStruct
using MyType = nv::MyStruct<int,float>;
(T, U) <-> (int, float)
const ${U}&@nv::MyStruct
thus becomes const float&
and ${T}*@nv::MyStruct
becomes int*
,template <typename T> inline auto componentMultiply(const Vec2<T>& lhs, const Vec2<T>& rhs) -> Vec2<T> { return {lhs[0] * rhs[0], lhs[1] * rhs[1]}; } template <typename T> inline auto componentDivide(const Vec2<T>& lhs, const Vec2<T>& rhs) -> Vec2<T> { return {lhs[0] / rhs[0], lhs[1] / rhs[1]}; }
namespace nvk { struct LuaGraphUpdater : public GraphUpdater { luna::LuaFunction func; explicit LuaGraphUpdater(lua_State* L, int index) : func(L, index, true){}; void update(FrameDesc& fdesc) override; }; void LuaGraphUpdater::update(FrameDesc& fdesc) { func(fdesc); } auto create_lua_graph_updater(luna::LuaFunction& func) -> nv::RefPtr<GraphUpdater> { return nv::create_ref_object<LuaGraphUpdater>(func.state, func.index); } }
namespace nvk { struct SimpleCubeRotator : public GraphUpdater { nv::RefPtr<PushConstants> pushc{nullptr}; U32 offset{0}; Vec3f axis; float speed; Mat4f viewProj; float startTime{-1.0F}; SimpleCubeRotator(PushConstants* pushc, const Vec3f& axis, float speed, const Mat4f& view, const Mat4f& proj, U32 offset) : pushc(pushc), offset(offset), viewProj(proj * view.inverse()), axis(axis), speed(speed){}; void update(FrameDesc& fdesc) override; }; void SimpleCubeRotator::update(FrameDesc& fdesc) { auto& barr = pushc->get_bytearray(); // compute the rotation angle: if (startTime < 0.0) { startTime = (float)fdesc.frameTime; } float elapsed = (float)fdesc.frameTime - startTime; float angle = toRad(elapsed * speed); // Create the rotation matrix: auto rot = Mat4f::rotate(angle, axis); // Compute the final transform matrix: auto mat = viewProj * rot; // Write the matrix into the push constant buffer: barr.write_mat4f(mat, offset); } auto create_simple_cube_rotator(PushConstants* pushc, const Vec3f& axis, float speed, const Mat4f& view, const Mat4f& proj, U32 offset) -> nv::RefPtr<GraphUpdater> { return nv::create_ref_object<SimpleCubeRotator>(pushc, axis, speed, view, proj, offset); }
static auto _bind_look_at_sig1(lua_State* L) -> int { nv::Mat4f* self = Luna< nv::Mat4f >::get(L,1); LUNA_ASSERT(self!=nullptr); nv::Vec3f* eye = Luna< nv::Vec3f >::get(L,2,false); nv::Vec3f* center = Luna< nv::Vec3f >::get(L,3,false); nv::Vec3f* up = Luna< nv::Vec3f >::get(L,4,false); nv::Mat4f res = self->look_at(*eye, *center, *up); nv::Mat4f* res_ptr = new nv::Mat4f(res); Luna< nv::Mat4f >::push(L, res_ptr, true); return 1; }
for i, ref_sig in ipairs(sigs) do local signame = ref_sig:getName() logDEBUG("Creating templated signature ", i, ": ", signame) local sig = func:createSignature(signame) sig:setVisibility(Vis.PUBLIC) sig:setDllImported(true) sig:setDefined(true) sig:setStatic(ref_sig:isStatic()) -- (More code here) end
-- Create on simple cube rotator: local axis = nv.Vec3f(1.0, 1.0, 1.0) local speed = 30.0 local view = nv.Mat4f.look_at(nv.Vec3f(0.0, 0.0, -50.0), nv.Vec3f(0.0, 0.0, 0.0), nv.Vec3f(0.0, -1.0, 0.0)) local proj = nv.Mat4f.perspective(math.rad(60.0), 1.333, 1.0, 100.0) -- local proj = nv.Mat4f.ortho(-30, 30, -30, 30, 0.0, 100.0) local updater = nvk.create_simple_cube_rotator(pushc, axis, speed, view, proj, 0) rgraph:add_updater(updater)
-- self:add_triangle(pline) self:add_cube(pline)
self:createVulkanEngine() self:createVulkanInstance() self:createVulkanDevice() -- self:createGLSLShaderModules("tests/test_pushconstants") self:createGLSLShaderModules("tests/test_cube_rotation")
void ByteArray::write_mat4f(const Mat4f& value, U64 idx, bool transpose) { U64 dataSize = sizeof(F32) * 16; if (transpose) { // transpose the matrix elements while copying: if (idx == U64_MAX) { idx = _position; } NVCHK(idx + dataSize <= _data.size(), "Out of range data write."); const F32* src = value.ptr(); F32* ptr = (F32*)get_data(idx); for (int i = 0; i < 4; ++i) { *ptr++ = *(src); *ptr++ = *(src + 4); *ptr++ = *(src + 8); *ptr++ = *(src + 12); src++; } _position = idx + dataSize; } else { // Write the matrix data directly: write_data((U8*)value.ptr(), dataSize, idx); } };
transpose()
anymore in the shader itself.