/* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. */ // the commands of a frame kept to be recorded into command buffers later (the stage M1 of the plan): the renderer makes the calls // of vkCmd* on a stream, which keeps them with their data in the order they were made. the stream is cut into chunks as it's // made, every so many draws: a chunk is recorded into a command buffer of its own, by a worker thread, and the command buffers go // to the queue in one submit in the order of the chunks. a chunk which starts inside a rendering begins it again resumed // (VK_RENDERING_RESUMING_BIT, the one before it ended it suspended) and sets again the state the draws before it left: the // pipelines, the descriptor sets, the pushed descriptors and constants, the vertex and index buffers and the dynamic state, as // the last calls of each kind made them. where the cuts are doesn't depend on the number of the threads, so the gpu gets the // same commands with any of them #pragma once #include "rendering/vulkan14/vulkan14common.h" #include #include #include namespace vk14 { class command_stream { public: // the draws, dispatches and copies a chunk holds at least before it's cut (inside a rendering, between two draws) static constexpr std::uint32_t chunk_work{256}; // the calls, with the arguments vkCmd* takes (the command buffer left out); the data they point to is copied. chains of pNext // aren't kept: none of the calls is made with one void begin_rendering(VkRenderingInfo const *Info); void end_rendering(); void pipeline_barrier2(VkDependencyInfo const *Dependency); void bind_pipeline(VkPipelineBindPoint BindPoint, VkPipeline Pipeline); void bind_descriptor_sets2(VkBindDescriptorSetsInfo const *Info); // Size: the bytes of the data of the template; BindPoint: the one the template was made for void push_descriptor_set_with_template2(VkPushDescriptorSetWithTemplateInfo const *Info, std::size_t Size, VkPipelineBindPoint BindPoint); void push_constants2(VkPushConstantsInfo const *Info); void bind_vertex_buffers(std::uint32_t First, std::uint32_t Count, VkBuffer const *Buffers, VkDeviceSize const *Offsets); void bind_index_buffer(VkBuffer Buffer, VkDeviceSize Offset, VkIndexType Type); void draw(std::uint32_t Vertices, std::uint32_t Instances, std::uint32_t FirstVertex, std::uint32_t FirstInstance); void draw_indexed(std::uint32_t Indices, std::uint32_t Instances, std::uint32_t FirstIndex, std::int32_t VertexOffset, std::uint32_t FirstInstance); void draw_indirect(VkBuffer Buffer, VkDeviceSize Offset, std::uint32_t Count, std::uint32_t Stride); void draw_indexed_indirect(VkBuffer Buffer, VkDeviceSize Offset, std::uint32_t Count, std::uint32_t Stride); void dispatch(std::uint32_t X, std::uint32_t Y, std::uint32_t Z); void dispatch_indirect(VkBuffer Buffer, VkDeviceSize Offset); void set_viewport(std::uint32_t First, std::uint32_t Count, VkViewport const *Viewports); void set_scissor(std::uint32_t First, std::uint32_t Count, VkRect2D const *Scissors); void set_cull_mode(VkCullModeFlags Mode); void set_front_face(VkFrontFace Face); void set_primitive_topology(VkPrimitiveTopology Topology); void set_depth_test_enable(VkBool32 Enable); void set_depth_write_enable(VkBool32 Enable); void set_depth_compare_op(VkCompareOp Op); void set_depth_bias_enable(VkBool32 Enable); void set_depth_bias(float Constant, float Clamp, float Slope); void set_primitive_restart_enable(VkBool32 Enable); void set_stencil_test_enable(VkBool32 Enable); void fill_buffer(VkBuffer Buffer, VkDeviceSize Offset, VkDeviceSize Size, std::uint32_t Data); void update_buffer(VkBuffer Buffer, VkDeviceSize Offset, VkDeviceSize Size, void const *Data); void copy_buffer2(VkCopyBufferInfo2 const *Info); void copy_image2(VkCopyImageInfo2 const *Info); void copy_buffer_to_image2(VkCopyBufferToImageInfo2 const *Info); void copy_image_to_buffer2(VkCopyImageToBufferInfo2 const *Info); void blit_image2(VkBlitImageInfo2 const *Info); void clear_color_image(VkImage Image, VkImageLayout Layout, VkClearColorValue const *Color, std::uint32_t Count, VkImageSubresourceRange const *Ranges); void clear_depth_stencil_image(VkImage Image, VkImageLayout Layout, VkClearDepthStencilValue const *Value, std::uint32_t Count, VkImageSubresourceRange const *Ranges); void write_timestamp2(VkPipelineStageFlags2 Stage, VkQueryPool Pool, std::uint32_t Query); void reset_query_pool(VkQueryPool Pool, std::uint32_t First, std::uint32_t Count); void begin_query(VkQueryPool Pool, std::uint32_t Query, VkQueryControlFlags Flags); void end_query(VkQueryPool Pool, std::uint32_t Query); void begin_debug_utils_label(VkDebugUtilsLabelEXT const *Label); void end_debug_utils_label(); // a checkpoint of VK_NV_device_diagnostic_checkpoints, with the number of the marker (vk14::pass_list) void set_checkpoint(std::uint64_t Marker); // the builds of acceleration structures (VK_KHR_acceleration_structure, gfx.raytracing), outside of a rendering void build_acceleration_structures(std::uint32_t Count, VkAccelerationStructureBuildGeometryInfoKHR const *Infos, VkAccelerationStructureBuildRangeInfoKHR const *const *Ranges); // a call of a library recording its own commands (the evaluation of dlss), outside of a rendering: Function gets the command // buffer of the replay and Data, which has to live until the replay is done. what it binds is left as it leaves it, so the // calls after it bind what they use again using external_function = void (*)(VkCommandBuffer Buffer, void *Data); void external(external_function Function, void *Data); // the calls of Other go to the gpu here, where this stream is now (the stage M2: the passes recorded by other threads at the same // time): the calls made after this one follow them, with the state they leave (the pipelines, the sets, the constants, the buffers, // the dynamic state), as if they were made in this stream. outside of a rendering and of a query; Other is a stream without // streams of its own spliced into it, finished before this one is, and kept until this one is replayed void splice(command_stream const &Other); // the stream goes to a queue of the compute family (the async compute of M6): the stages and the accesses of its barriers which such // a queue doesn't have are left out. the work of the graphics queue they wait for is done before it through the semaphores void set_compute_queue(bool Compute) { m_compute = Compute; } // the frame goes to the queue in two submits from here on, the calls after this one in the second, which waits for the work of the // compute queue (M6). outside of a rendering and of a query; the first one made counts void split(); // the first chunk of the second submit (chunks() when nothing was split), valid after finish() std::size_t split_chunk() const { return m_splitchunk; } // the stream is made anew void clear(); bool empty() const; // the stream is ended: no calls after it void finish(); // the chunks, valid after finish(); those of the streams spliced into it, in their places std::size_t chunks() const { return m_pieces.empty() ? m_chunks.size() : m_order.size(); } // the calls recorded since the start, with those of the streams spliced into it std::size_t calls() const; // the chunk recorded into Buffer (begun already, ended by the caller) void replay(std::size_t Chunk, VkCommandBuffer Buffer) const; // all of it into one command buffer void replay(VkCommandBuffer Buffer) const; private: // a call of a stream, the stream itself or one spliced into it using call_ref = std::pair; // a part of the stream as the gpu gets it: chunks of the stream itself or of a stream spliced into it, the state the calls before // it left (the calls they made last of each kind, in the order they were made) and the state the part leaves with them struct piece { command_stream const *stream{nullptr}; std::size_t first{0}; // chunks of the stream std::size_t end{0}; std::vector inherited; // the calls of the part setting the state last, kept when the part is of the stream itself (splice()) std::vector state; std::vector pushes; }; // the calls setting the state, of all the parts so far: a slot each (state_slot), and the pushes of the constants of each range // and stages; the part of each, for their order struct state_ref { command_stream const *stream{nullptr}; std::size_t call{0}; std::size_t piece{0}; }; struct merged_state { std::vector slots; std::vector pushes; }; // the state the calls of the part leave behind, over the state the parts before it left void leave_state(std::size_t Piece, merged_state &State) const; // the calls of the state a chunk sets again; the dynamic states have a slot each enum state_slot : std::uint32_t { pipeline_graphics, pipeline_compute, viewport, scissor, cull_mode, front_face, topology, depth_test, depth_write, depth_compare, depth_bias_enable, depth_bias, primitive_restart, stencil_test, index_buffer, sets_graphics, // set_slots of each sets_compute = sets_graphics + 8, pushed_graphics = sets_compute + 8, pushed_compute = pushed_graphics + 8, vertex_buffers = pushed_compute + 8, // vertex_slots slot_count = vertex_buffers + 8 }; static constexpr std::size_t no_call{~static_cast(0)}; static constexpr std::uint32_t set_slots{8}; static constexpr std::uint32_t vertex_slots{8}; struct chunk { std::size_t first{0}; // offset of its first call in the data std::size_t end{0}; // past its last one // the begin of the rendering the chunk starts inside of, resumed, and the calls of the state set again, in their order std::size_t resumed{no_call}; std::vector state; // the begin of the rendering the chunk ends inside of, suspended std::size_t suspended{no_call}; }; // room for a call of Type with Size bytes of its own data after its header, aligned; the offset of the call std::size_t add(std::uint32_t Type, std::size_t Size); template Type_ &payload(std::size_t Call) { return *reinterpret_cast(m_data.data() + Call + header_size); } template Type_ const &payload(std::size_t Call) const { return *reinterpret_cast(m_data.data() + Call + header_size); } template Type_ const *at(std::size_t Offset) const { return reinterpret_cast(m_data.data() + Offset); } // copies Bytes of Data after the call made last (aligned), returns their offset in the data std::size_t append(void const *Data, std::size_t Bytes); // the call made last ends here void close(std::size_t Call); void remember(std::uint32_t Slot, std::size_t Call) { m_state[Slot] = Call; } // a draw, dispatch or copy was added: the chunk is cut after it when it has enough of them void work(); void cut(); // Flags: added to those of a begin of a rendering void execute(std::size_t Call, VkCommandBuffer Buffer, VkRenderingFlags Flags = 0) const; // a chunk of this stream alone, and the calls between two offsets void replay_chunk(std::size_t Chunk, VkCommandBuffer Buffer) const; void replay_calls(std::size_t First, std::size_t End, VkCommandBuffer Buffer) const; static constexpr std::size_t header_size{8}; std::vector m_data; std::vector m_chunks; std::array m_state{}; // the pushes of the constants: the last one of each range and stages, in their order std::vector m_pushes; std::size_t m_rendering{no_call}; // the begin of the rendering under way std::uint32_t m_queries{0}; // queries active std::uint32_t m_work{0}; // of the chunk under way std::size_t m_calls{0}; bool m_finished{false}; // the parts of the stream with the streams spliced into it, empty without them; the first chunk of the part of the stream itself // under way; the chunks in the order of the parts (the part, the chunk of its stream) std::vector m_pieces; std::size_t m_piecefirst{0}; std::vector> m_order; bool m_compute{false}; // the chunk of the stream itself the split begins, and the first chunk of the second submit std::size_t m_split{no_call}; std::size_t m_splitchunk{0}; }; } // namespace vk14