Continuing our journey in vulkan, this time our target will be to introduce support for push constants in our command buffers. That sounds simple at first, but when I starting thinking about it, I realize it might not be that simple in fact. Let's check why…
local pdev = self.vkeng:get_physical_device() local props = pdev:get_properties() logDEBUG("Max push constant size: ", props.limits.maxPushConstantsSize, " bytes")
Max push constant size: 256 bytes
-- Define a single push constant range: pc:setStageFlags(vk.ShaderStageFlagBits.VERTEX_BIT + vk.ShaderStageFlagBits.FRAGMENT_BIT) pc:setOffset(0) pc:setSize(32)
void VulkanCommandBuffer::write_push_contants(VkPipelineLayout layout, VkShaderStageFlags stages, U32 offset, U32 size, const void* data) { ASSERT(is_recording()); _device->vkCmdPushConstants(_buffer, layout, stages, offset, size, data); }
class NVCORE_EXPORT ByteArray { public: ByteArray(); explicit ByteArray(U64 size, U8 defVal = 0); ByteArray(const ByteArray& rhs); ByteArray(ByteArray&& rhs) noexcept; auto operator=(const ByteArray& rhs) -> ByteArray&; auto operator=(ByteArray&& rhs) noexcept -> ByteArray&; virtual ~ByteArray(); /** Retrieve the size of this array */ [[nodiscard]] auto get_size() const -> U64 { return _data.size(); } /** Retrieve the data pointer */ [[nodiscard]] auto get_data(U64 offset = 0) const -> const U8* { return _data.data() + offset; } [[nodiscard]] auto get_data(U64 offset = 0) -> U8* { return _data.data() + offset; } /** Get the current position */ [[nodiscard]] auto get_position() const -> U64 { return _position; } /** Set the current position */ void set_position(U64 pos) { CHECK(pos < _data.size(), "out of range position."); _position = pos; }; /** Reset the position in this buffer. */ void reset_position() { _position = 0; } /** Resize the array */ void resize(U64 newSize) { _data.resize(newSize); } /** Set a given value at a given byte position */ void write_data(const U8* data, U64 dataSize, U64 idx = U64_MAX); void write_u8(U8 value, U64 idx = U64_MAX); void write_i8(I8 value, U64 idx = U64_MAX); void write_u16(U16 value, U64 idx = U64_MAX); void write_i16(I16 value, U64 idx = U64_MAX); void write_u32(U32 value, U64 idx = U64_MAX); void write_i32(I32 value, U64 idx = U64_MAX); void write_u64(U64 value, U64 idx = U64_MAX); void write_i64(I64 value, U64 idx = U64_MAX); void write_f32(F32 value, U64 idx = U64_MAX); void write_f64(F64 value, U64 idx = U64_MAX); void write_vec2f(const Vec2f& value, U64 idx = U64_MAX); void write_vec3f(const Vec3f& value, U64 idx = U64_MAX); void write_vec4f(const Vec4f& value, U64 idx = U64_MAX); protected: /** Storage for the data of this byte array. */ nv::Vector<U8> _data; /** Current position in the buffer. */ U64 _position{0}; };
layout(push_constant) uniform Push { vec4 offset; vec4 scale; } push; #ifdef _VERTEX_ layout(location = 0) in vec2 position; layout(location = 1) in vec4 color; layout(location=0) out vec3 vertColor; void main() { gl_Position = vec4(position*push.scale.zw + push.offset.xy, 0.0, 1.0); vertColor = color.rgb; } #endif #ifdef _FRAGMENT_ layout(location=0) in vec3 fragColor; layout(location=0) out vec4 outColor; void main() { outColor = vec4(fragColor, 1.0); } #endif
-- We can also store our push constants array here: self.pushArr = nv.ByteArray(32) self.pushArr:write_vec4f(nv.Vec4f(0.5, 0.5, 0.0, 0.0)) self.pushArr:write_vec4f(nv.Vec4f(0.0, 0.0, 0.4, 0.4))
-- Prepare the push constant wrapper: local pc = nvk.VulkanPushConstantRange() -- Define a single push constant range: pc:setStageFlags(vk.ShaderStageFlagBits.VERTEX_BIT + vk.ShaderStageFlagBits.FRAGMENT_BIT) pc:setOffset(0) pc:setSize(32) -- 32 bytes = 2*4*4 => 2 vec4 lines
-- add the push constants: cbuf:write_push_contants(playout, pstages, 0, 32, self.pushArr:get_data())
-- Now we create a Command Pool on that family index: self.cmdpool = self.vkeng:create_command_pool(famIdx, vk.CommandPoolCreateFlagBits.RESET_COMMAND_BUFFER_BIT + vk.CommandPoolCreateFlagBits.TRANSIENT_BIT) logDEBUG("Created command pool");
local cb = nv.Callback(function() self:recordCommandBuffers(rpass, vbuf, cfg) end) self.renderer:on_swapchain_updated(cb)
auto _lunactr_CmdBuffersProvider(luna::LuaFunction& func) -> CmdBuffersProvider*;
struct LuaCmdBuffersProvider : public CmdBuffersProvider { NV_DECLARE_NO_COPY(LuaCmdBuffersProvider) NV_DECLARE_NO_MOVE(LuaCmdBuffersProvider) LuaCmdBuffersProvider(lua_State* L, I32 idx) : func(L, idx, true) {} ~LuaCmdBuffersProvider() override = default; void get_buffers(FrameDesc& fdesc, VkCommandBufferList& buffers) override { // Call the lua function: func(fdesc, buffers); } LuaFunction func; }; auto _lunactr_CmdBuffersProvider(luna::LuaFunction& func) -> CmdBuffersProvider* { auto cb = nv::create_ref_object<LuaCmdBuffersProvider>(func.state, func.index); return cb.release(); }
-- Generate the command buffers for a given frame description -- just before the those buffer are submitted to the graphics queue ---@param fdesc nvk.FrameDesc Current frame description ---@param buffers nvk.VkCommandBufferList list of vk buffers. ---@param rpass nvk.VulkanRenderPass Render pass to use ---@param vbuf nvk.VulkanVertexBuffer Vertex buffer object ---@param playout vk.PipelineLayout_T Pipeline layout ---@param cfg nvk.VulkanGraphicsPipelineCreateInfo create pipeline config function Class:generate_cmd_buffer(fdesc, buffers, rpass, vbuf, playout, cfg) -- logDEBUG("Should provide the cmd buffer for frame ", fdesc.frameNumber, " on image ", fdesc.swapchainImageIndex) local idx = fdesc.swapchainImageIndex -- Re-record the command buffer as above: local cbuf = self.cbufs:at(idx) local fbuf = self.renderer:get_swapchain_framebuffer(idx) -- Push constants stages: local pstages = vk.ShaderStageFlagBits.VERTEX_BIT + vk.ShaderStageFlagBits.FRAGMENT_BIT local width = self.renderer:get_swapchain_width() local height = self.renderer:get_swapchain_height() if self.width ~= width or self.height ~= height then -- We rebuild the pipeline with teh correct size: self.width = width self.height = height -- Now we should update the viewport dimensions in our graphics pipeline config: local vp = cfg:getCurrentViewportState() vp:setViewport(width, height) if self.pipeline ~= nil then self.vkeng:remove_pipeline(self.pipeline) end self.pipeline = self.vkeng:create_graphics_pipeline(cfg, self.pipelineCache) end -- Check that we can reset the command buffer: fbuf:set_clear_color(0, 0.2, 0.2, 0.2, 1.0) -- fbuf:set_clear_color(0, 1, 1, 1, 1.0) -- cbuf:begin(vk.CommandBufferUsageFlagBits.ONE_TIME_SUBMIT_BIT) cbuf:begin(0) -- Begin rendering into the swapchain framebuffer: cbuf:begin_inline_pass(rpass, fbuf) -- Bind the graphics pipeline: cbuf:bind_graphics_pipeline(self.pipeline) -- Bind the vertex buffer: cbuf:bind_vertex_buffer(vbuf, 0) -- add the push constants: cbuf:write_push_contants(playout, pstages, 0, 32, self.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()) end
local func = function(fdesc, buffers) self:generate_cmd_buffer(fdesc, buffers, renderpass, vbuf, playout, cfg) end local prov = nvk.CmdBuffersProvider(func) self.renderer:set_cmd_buffer_provider(prov)
#include <vulkan_precomp.h> #include <base/VulkanCommandBuffer.h> #include <base/VulkanFramebuffer.h> #include <base/VulkanPipeline.h> #include <base/VulkanPipelineCache.h> #include <base/VulkanPipelineLayout.h> #include <base/VulkanRenderPass.h> #include <engine/VulkanRenderer.h> #include <engine/VulkanVertexBuffer.h> #include <providers/SimpleCmdBuffersProvider.h> #include <vulkan_wrappers.h> namespace nvk { SimpleCmdBuffersProvider::SimpleCmdBuffersProvider( VulkanRenderer* renderer, VulkanRenderPass* rpass, VulkanVertexBuffer* vbuf, VulkanPipelineLayout* playout, VulkanGraphicsPipelineCreateInfo* cfg, VulkanPipelineCache* pcache, const VulkanCommandBufferList& cbufs, const nv::ByteArray& pushArr) : _renderer(renderer), _pipelineCache(pcache), _rpass(rpass), _vbuf(vbuf), _playout(playout), _cfg(cfg), _cbufs(cbufs), _pushArr(pushArr) {} 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); // 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/
// 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);
#define M_PI 3.1415926535897932384626433832795 void main() { // get the time value: float time = push.offset.z; // prepare rotation matrix: float theta = 2.0*M_PI*time/5.0; float ct = cos(theta); float st = sin(theta); mat2 rot = mat2(ct,st,-st,ct); // Rotate position: vec2 pos = position*rot; gl_Position = vec4(pos*push.scale.zw, 0.0, 1.0); // gl_Position = vec4(position*push.scale.zw + push.offset.xy, 0.0, 1.0); vertColor = color.rgb; }