VEX Engine
Retro looking game engine made in vulkan mostly because i wanted to understand it better.
Loading...
Searching...
No Matches
VulkanMesh.cpp
1#include "VulkanMesh.hpp"
2#include "components/Mesh.hpp"
3#include "components/PathUtils.hpp"
4#include "glm/common.hpp"
5#include "glm/fwd.hpp"
6
7#include <SDL3/SDL.h>
8
9// debug
10#ifdef _WIN32
11// pass
12#else
13#include <X11/X.h>
14#endif
15#include <cstdint>
16#include <immintrin.h>
17#include <cstring>
18#include <iostream>
19
20namespace vex {
21 VulkanMesh::VulkanMesh(VulkanContext& context) : m_r_context(context) {
22 log("VulkanMesh created");
23 }
24
25 VulkanMesh::~VulkanMesh() {
26 log("Destroying VulkanMesh");
27 vkDeviceWaitIdle(m_r_context.device);
28
29 for (auto& submesh : m_submeshBuffers) {
30 if (submesh.vertexBuffer != VK_NULL_HANDLE) {
31 vmaDestroyBuffer(m_r_context.allocator, submesh.vertexBuffer, submesh.vertexAlloc);
32 submesh.vertexBuffer = VK_NULL_HANDLE;
33 }
34 if (submesh.indexBuffer != VK_NULL_HANDLE) {
35 vmaDestroyBuffer(m_r_context.allocator, submesh.indexBuffer, submesh.indexAlloc);
36 submesh.indexBuffer = VK_NULL_HANDLE;
37 }
38 }
39 }
40
41 void VulkanMesh::StreamToGPU(void* dst, const void* src, size_t sizeBytes) {
42 char* dstPtr = static_cast<char*>(dst);
43 const char* srcPtr = static_cast<const char*>(src);
44
45 size_t dstAddr = reinterpret_cast<size_t>(dstPtr);
46 size_t misalignment = dstAddr & 15;
47 size_t head = (misalignment == 0) ? 0 : (16 - misalignment);
48
49 if (head > sizeBytes) head = sizeBytes;
50
51 if (head > 0) {
52 std::memcpy(dstPtr, srcPtr, head);
53 dstPtr += head;
54 srcPtr += head;
55 sizeBytes -= head;
56 }
57
58 size_t vecSize = sizeof(__m128i);
59 size_t loops = sizeBytes / vecSize;
60
61 auto* dst128 = reinterpret_cast<__m128i*>(dstPtr);
62 auto* src128 = reinterpret_cast<const __m128i*>(srcPtr);
63
64 for (size_t i = 0; i < loops; ++i) {
65 __m128i data = _mm_loadu_si128(src128 + i);
66 _mm_stream_si128(dst128 + i, data);
67 }
68
69 size_t bytesHandled = loops * vecSize;
70 size_t tail = sizeBytes - bytesHandled;
71
72 if (tail > 0) {
73 std::memcpy(dstPtr + bytesHandled, srcPtr + bytesHandled, tail);
74 }
75
76 _mm_sfence();
77 }
78
79 void VulkanMesh::upload(const MeshData& meshData) {
80 log("Uploading mesh with %zu submeshes", meshData.submeshes.size());
81
82 m_submeshBuffers.reserve(meshData.submeshes.size());
83 m_submeshTextures.reserve(meshData.submeshes.size());
84 m_cpuSubmeshData = meshData.submeshes;
85
86 for (auto& srcSubmesh : m_cpuSubmeshData) {
87 SubmeshBuffers buffers{};
88
89 VkBufferCreateInfo bufferInfo{};
90 bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
91 bufferInfo.size = srcSubmesh.vertices.size() * sizeof(Vertex);
92 bufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
93
94 VmaAllocationCreateInfo allocInfo{};
95 allocInfo.usage = VMA_MEMORY_USAGE_CPU_TO_GPU;
96 vmaCreateBuffer(m_r_context.allocator, &bufferInfo, &allocInfo,
97 &buffers.vertexBuffer, &buffers.vertexAlloc, nullptr);
98
99 void* data;
100 vmaMapMemory(m_r_context.allocator, buffers.vertexAlloc, &data);
101 StreamToGPU(data, srcSubmesh.vertices.data(), bufferInfo.size);
102 vmaUnmapMemory(m_r_context.allocator, buffers.vertexAlloc);
103
104 bufferInfo.size = srcSubmesh.indices.size() * sizeof(uint32_t);
105 bufferInfo.usage = VK_BUFFER_USAGE_INDEX_BUFFER_BIT;
106 vmaCreateBuffer(m_r_context.allocator, &bufferInfo, &allocInfo,
107 &buffers.indexBuffer, &buffers.indexAlloc, nullptr);
108
109 vmaMapMemory(m_r_context.allocator, buffers.indexAlloc, &data);
110 StreamToGPU(data, srcSubmesh.indices.data(), bufferInfo.size);
111 vmaUnmapMemory(m_r_context.allocator, buffers.indexAlloc);
112
113 buffers.indexCount = static_cast<uint32_t>(srcSubmesh.indices.size());
114
115 m_submeshBuffers.push_back(buffers);
116 m_submeshTextures.push_back(srcSubmesh.texturePath);
117
118 for (size_t i = 0; i < srcSubmesh.indices.size(); i += 3) {
119 const auto& p0 = srcSubmesh.vertices[srcSubmesh.indices[i+0]].position;
120 const auto& p1 = srcSubmesh.vertices[srcSubmesh.indices[i+1]].position;
121 const auto& p2 = srcSubmesh.vertices[srcSubmesh.indices[i+2]].position;
122 srcSubmesh.triangleCenters.push_back((p0 + p1 + p2) / 3.0f);
123 }
124
125 log("Uploaded submesh: %zu vertices, %u indices, texture: '%s'",
126 srcSubmesh.vertices.size(), buffers.indexCount,
127 srcSubmesh.texturePath.c_str());
128 }
129 }
130
131 void VulkanMesh::extractTransparentTriangles(
132 const glm::mat4& modelMatrix,
133 const glm::vec3& cameraPos,
134 uint32_t modelIndex,
135 uint32_t frameIndex,
136 vex::Entity entity,
137 std::vector<TransparentTriangle>& outTriangles
138 ) {
139 glm::mat3 rotationScaleMatrix = glm::mat3(modelMatrix);
140 glm::vec3 translationVector = glm::vec3(modelMatrix[3]);
141
142 for (uint32_t submeshIndex = 0; submeshIndex < m_cpuSubmeshData.size(); ++submeshIndex) {
143 const auto& submesh = m_cpuSubmeshData[submeshIndex];
144 const size_t indexCount = submesh.indices.size();
145 const glm::vec3* centers = submesh.triangleCenters.data();
146
147 for (uint32_t i = 0; i < indexCount; i += 3) {
148 glm::vec3 centerWorld = rotationScaleMatrix * centers[i/3] + translationVector;
149
150 glm::vec3 d = centerWorld - cameraPos;
151 float distanceSq = glm::dot(d, d);
152
153 outTriangles.push_back({
154 distanceSq,
155 modelIndex,
156 frameIndex,
157 i,
158 submeshIndex,
159 this,
160 entity
161 });
162 }
163 }
164 }
165
167 VkCommandBuffer cmd,
168 VkPipelineLayout pipelineLayout,
169 VulkanResources& resources,
170 uint32_t frameIndex,
171 uint32_t modelIndex,
172 uint32_t submeshIndex,
173 glm::mat4 modelMatrix,
174 bool modelChanged,
175 bool submeshChanged,
176 const MeshComponent& mc
177 ) const {
178 const auto& buffers = m_submeshBuffers[submeshIndex];
179 const auto& textureName = m_submeshTextures[submeshIndex];
180
181 if(modelChanged){
182 uint32_t lightsDynamicOffset = modelIndex * static_cast<uint32_t>(sizeof(SceneLightsUBO));
183 uint32_t dynamicOffsets[1] = { lightsDynamicOffset };
184
185 VkDescriptorSet globalSet = resources.getDescriptorSet(frameIndex);
186 vkCmdBindDescriptorSets(
187 cmd,
188 VK_PIPELINE_BIND_POINT_GRAPHICS,
189 pipelineLayout,
190 0,
191 1,
192 &globalSet,
193 1,
194 dynamicOffsets
195 );
196 }
197
198 uint32_t textureIndex = resources.getTextureIndex(textureName);
199 if (textureIndex >= MAX_TEXTURES) {
200 textureIndex = 0;
201 }
202
203 if (m_r_context.supportsBindlessTextures) {
204
205 }
206 else {
207 if(submeshChanged || modelChanged){
208 VkDescriptorSet texSet = resources.getTextureDescriptorSet(frameIndex, textureIndex);
209 vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS,
210 pipelineLayout, 1, 1, &texSet,
211 0, nullptr);
212 }
213 }
214
215 bool needPush = modelChanged;
216 if (m_r_context.supportsBindlessTextures && submeshChanged) {
217 needPush = true;
218 }
219
220 if(needPush){
221 PushConstants modelPush{};
222 modelPush.model = modelMatrix;
223 modelPush.color = mc.color;
224 modelPush.textureID = textureIndex;
225
226 if (!resources.textureExists(textureName) && !textureName.empty()) {
227 log(LogLevel::WARNING, "Missing texture...");
228 }
229
230 vkCmdPushConstants(
231 cmd,
232 pipelineLayout,
233 VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT,
234 0,
235 sizeof(PushConstants),
236 &modelPush
237 );
238 }
239
240 VkBuffer vertexBuffers[] = {buffers.vertexBuffer};
241 VkDeviceSize offsets[] = {0};
242 vkCmdBindVertexBuffers(cmd, 0, 1, vertexBuffers, offsets);
243 vkCmdBindIndexBuffer(cmd, buffers.indexBuffer, 0, VK_INDEX_TYPE_UINT32);
244 }
245
246 void VulkanMesh::draw(VkCommandBuffer cmd, VkPipelineLayout pipelineLayout,
247 VulkanResources& resources, uint32_t frameIndex, uint32_t modelIndex, glm::mat4 modelMatrix, const MeshComponent& mc) const {
248
249 std::string currentTexture = "";
250
251 uint32_t lightsDynamicOffset = modelIndex * static_cast<uint32_t>(sizeof(SceneLightsUBO));
252 uint32_t dynamicOffsets[1] = { lightsDynamicOffset };
253
254 VkDescriptorSet globalSet = resources.getDescriptorSet(frameIndex);
255 vkCmdBindDescriptorSets(
256 cmd,
257 VK_PIPELINE_BIND_POINT_GRAPHICS,
258 pipelineLayout,
259 0,
260 1,
261 &globalSet,
262 1,
263 dynamicOffsets
264 );
265
266 for (size_t i = 0; i < m_submeshBuffers.size(); i++) {
267 const auto& buffers = m_submeshBuffers[i];
268 std::string textureName = "";
269 uint32_t textureIndex = 0;
270 if(mc.textureOverrides.contains(i)){
271 textureName = GetAssetPath(mc.textureOverrides.at(i));
272 textureIndex = resources.getTextureIndex(textureName);
273
274 if(textureIndex == 0){
275 textureIndex = resources.getTextureIndex(m_submeshTextures[i]);
276 }
277 }else{
278 textureName = m_submeshTextures[i];
279 textureIndex = resources.getTextureIndex(m_submeshTextures[i]);
280 }
281
282 //log("Invalid texture index %u for '%s'", textureIndex, textureName.c_str());
283
284 if (textureIndex >= MAX_TEXTURES) {
285 SDL_LogError(SDL_LOG_CATEGORY_RENDER,
286 "Invalid texture index %u for '%s' (Max: %u)",
287 textureIndex, textureName.c_str(), MAX_TEXTURES);
288 textureIndex = 0;
289 }
290
291 const bool textureExists = !textureName.empty() && resources.textureExists(textureName);
292
293 //log("Texture exists: %s, textureName: %s, textureIndex: %d", textureExists ? "true" : "false", textureName.c_str(), textureIndex);
294
295 if (!textureExists) {
296 textureName = "default";
297 }
298
299 PushConstants modelPush{};
300
301 if (m_r_context.supportsBindlessTextures) {
302 PushConstants modelPush{};
303 modelPush.color = mc.color;
304 modelPush.model = modelMatrix;
305 modelPush.textureID = textureIndex;
306
307 vkCmdPushConstants(
308 cmd,
309 pipelineLayout,
310 VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT,
311 0,
312 sizeof(PushConstants),
313 &modelPush
314 );
315 }
316 else {
317 if (currentTexture != textureName) {
318 VkDescriptorSet texSet = resources.getTextureDescriptorSet(frameIndex, textureIndex);
319 vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 1, 1, &texSet, 0, nullptr);
320 currentTexture = textureName;
321 }
322
323 PushConstants modelPush{};
324 modelPush.color = mc.color;
325 modelPush.model = modelMatrix;
326
327 vkCmdPushConstants(cmd, pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(PushConstants), &modelPush);
328 }
329
330 VkBuffer vertexBuffers[] = {buffers.vertexBuffer};
331 VkDeviceSize offsets[] = {0};
332 vkCmdBindVertexBuffers(cmd, 0, 1, vertexBuffers, offsets);
333 vkCmdBindIndexBuffer(cmd, buffers.indexBuffer, 0, VK_INDEX_TYPE_UINT32);
334
335 vkCmdDrawIndexed(cmd, buffers.indexCount, 1, 0, 0, 0);
336 }
337 }
338}
This file defines MeshData struct and all other structs needed for it.
This file defines VulkanMesh class.
void StreamToGPU(void *dst, const void *src, size_t sizeBytes)
Helper function to stream data to GPU memory.
VulkanMesh(VulkanContext &context)
Constructor for VulkanMesh class.
void draw(VkCommandBuffer cmd, VkPipelineLayout pipelineLayout, VulkanResources &resources, uint32_t frameIndex, uint32_t modelIndex, glm::mat4 modelMatrix, const MeshComponent &mc) const
Draws the mesh to the screen.
void upload(const MeshData &meshData)
Uploads mesh data to the GPU.
void bindAndDrawBatched(VkCommandBuffer cmd, VkPipelineLayout pipelineLayout, VulkanResources &resources, uint32_t frameIndex, uint32_t modelIndex, uint32_t submeshIndex, glm::mat4 modelMatrix, bool modelChanged, bool submeshChanged, const MeshComponent &mc) const
Batch-optimized draw call for sorted transparent triangles.
This class manages resources like textures, descriptor sets, and uniform buffers.
Definition Resources.hpp:23
uint32_t getTextureIndex(const std::string &name)
Returns the index of a texture with the given name.
bool textureExists(const std::string &name) const
Returns if a texture with the given name exists.
VkDescriptorSet getTextureDescriptorSet(uint32_t frameIndex, uint32_t textureIndex) const
Returns a descriptor set for the given frame index and texture index.
VkDescriptorSet getDescriptorSet(uint32_t frameIndex) const
Returns the descriptor set for the given frame index.
const uint32_t MAX_TEXTURES
Definition limits.hpp:13
uint32_t Entity
Type alias representing a unique entity identifier in the ECS system.
Definition Types.hpp:11
void VEX_EXPORT log(const char *fmt,...)
Logs a formatted message.
std::string VEX_EXPORT GetAssetPath(const std::string &relativePath)
Resolves the absolute path of an asset based on the current build configuration.
Definition PathUtils.cpp:73
Struct containing raw meshData, mesh id, texture paths and material properties. It just template and ...
Mesh data structure for loading and managing mesh data.
Definition Mesh.hpp:41
Push constants, holds model information.
Definition uniforms.hpp:53
Scene lights uniform buffer object.
Definition uniforms.hpp:47
Vertex structure for mesh data.
Definition Mesh.hpp:25
Struct holding all vulkan data, like device, surface, swapchain, images, views, and more.
Definition context.hpp:26
struct used to hold buffers and allocations for each submesh in single object.