libmx2 2.2.1
C++20 multimedia and game-engine library (SDL2 / OpenGL / Vulkan)
Loading...
Searching...
No Matches
vk_abstract_model.cpp
Go to the documentation of this file.
2#include"loadpng.hpp"
3
4namespace mx {
5
6 void VKAbstractModel::load(VKWindow *win, const std::string &model_file, const std::string &texture_file, const std::string &path, float scale) {
7 std::cout << ">> VKAbstractModel::load: model=" << model_file << ", textures=" << texture_file << ", scale=" << scale << "\n";
8 obj.load(model_file, scale);
9 const auto &verts = obj.vertices();
10 if (!verts.empty()) {
11 float minX = verts[0].pos[0], maxX = minX;
12 float minY = verts[0].pos[1], maxY = minY;
13 float minZ = verts[0].pos[2], maxZ = minZ;
14 for (const auto &v : verts) {
15 minX = std::min(minX, v.pos[0]); maxX = std::max(maxX, v.pos[0]);
16 minY = std::min(minY, v.pos[1]); maxY = std::max(maxY, v.pos[1]);
17 minZ = std::min(minZ, v.pos[2]); maxZ = std::max(maxZ, v.pos[2]);
18 }
19 modelCenterOffset = glm::vec3(
20 -0.5f * (minX + maxX),
21 -0.5f * (minY + maxY),
22 -0.5f * (minZ + maxZ));
23 float maxExtent = std::max(maxX - minX, std::max(maxY - minY, maxZ - minZ));
24 if (maxExtent > 1e-6f)
25 modelRenderScale = 2.5f / maxExtent;
26 }
27
28 obj.upload(win->getDevice(), win->getPhysicalDevice(), win->getCommandPool(), win->getGraphicsQueue());
29 std::cout << ">> VKAbstractModel::load: model uploaded, center offset=(" << modelCenterOffset.x << ", " << modelCenterOffset.y << ", " << modelCenterOffset.z << "), renderScale=" << modelRenderScale << "\n";
30 loadModelTextures(win, texture_file, path);
31 const size_t frameCount = win->swapChainImages.size();
32 modelUniformBuffers.resize(frameCount);
33 modelUniformBufferMemory.resize(frameCount);
34 modelUniformBuffersMapped.resize(frameCount, nullptr);
35 for (size_t i = 0; i < frameCount; ++i) {
37 VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
38 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
40 vkMapMemory(win->getDevice(), modelUniformBufferMemory[i], 0,
42 }
43
46 std::cout << ">> VKAbstractModel::load: complete [OK]\n";
47 }
48
50 std::cout << ">> VKAbstractModel::loadInstance: creating lightweight instance\n";
51 isInstance_ = true;
54 // Share texture entries (source owns the GPU resources)
56
57 const size_t frameCount = win->swapChainImages.size();
58 modelUniformBuffers.resize(frameCount);
59 modelUniformBufferMemory.resize(frameCount);
60 modelUniformBuffersMapped.resize(frameCount, nullptr);
61 for (size_t i = 0; i < frameCount; ++i) {
63 VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
64 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
66 vkMapMemory(win->getDevice(), modelUniformBufferMemory[i], 0,
68 }
71 std::cout << ">> VKAbstractModel::loadInstance: complete [OK]\n";
72 }
73
75 for (size_t i = 0; i < modelUniformBuffers.size(); ++i) {
77 vkUnmapMemory(win->getDevice(), modelUniformBufferMemory[i]);
78 if (modelUniformBuffers[i] != VK_NULL_HANDLE)
79 vkDestroyBuffer(win->getDevice(), modelUniformBuffers[i], nullptr);
80 if (modelUniformBufferMemory[i] != VK_NULL_HANDLE)
81 vkFreeMemory(win->getDevice(), modelUniformBufferMemory[i], nullptr);
82 }
83 modelUniformBuffers.clear();
86 if (modelDescriptorPool != VK_NULL_HANDLE) {
87 vkDestroyDescriptorPool(win->getDevice(), modelDescriptorPool, nullptr);
88 modelDescriptorPool = VK_NULL_HANDLE;
89 }
90 modelDescriptorSets.clear();
91 // Don't destroy textures or mesh — source model owns them
92 modelTextures.clear();
93 }
94
95 void VKAbstractModel::setShaders(VKWindow *win, const std::string &vert, const std::string &frag) {
96 vertShaderPath_ = vert;
97 fragShaderPath_ = frag;
98 if (modelPipeline != VK_NULL_HANDLE) {
99 vkDestroyPipeline(win->getDevice(), modelPipeline, nullptr);
100 modelPipeline = VK_NULL_HANDLE;
101 }
102 if (modelPipelineWireframe != VK_NULL_HANDLE) {
103 vkDestroyPipeline(win->getDevice(), modelPipelineWireframe, nullptr);
104 modelPipelineWireframe = VK_NULL_HANDLE;
105 }
107 std::cout << ">> VKAbstractModel::setShaders: vert=" << vert << ", frag=" << frag << " [OK]\n";
108 }
109
111
112 }
113
114 void VKAbstractModel::updateUBO(VKWindow *win, uint32_t imageIndex, const mx::UniformBufferObject &ubo) {
115 if (imageIndex < modelUniformBuffersMapped.size() && modelUniformBuffersMapped[imageIndex])
116 memcpy(modelUniformBuffersMapped[imageIndex], &ubo, sizeof(ubo));
117 }
118
119 void VKAbstractModel::loadModelTextures(VKWindow *win, const std::string &texture_file, const std::string &path) {
120 std::cout << ">> VKAbstractModel::loadModelTextures: loading from " << texture_file << "\n";
121 std::string texPath = texture_file;
122 std::ifstream tf(texPath);
123 if (!tf.is_open())
124 throw mx::Exception("Failed to open .tex file: " + texPath);
125
126 std::string prefix = path;
127 std::vector<std::string> imagePaths;
128
129 // Read all meaningful lines to detect format
130 std::vector<std::string> lines;
131 {
132 std::string line;
133 while (std::getline(tf, line)) {
134 size_t b = line.find_first_not_of(" \t\r\n");
135 if (b == std::string::npos) continue;
136 size_t e = line.find_last_not_of(" \t\r\n");
137 line = line.substr(b, e - b + 1);
138 if (line.empty() || line[0] == '#') continue;
139 lines.push_back(line);
140 }
141 }
142
143 // Detect structured format by looking for submesh keyword
144 bool structured = false;
145 bool isMTL = false;
146 for (const auto &ln : lines) {
147 std::istringstream chk(ln);
148 std::string kw;
149 chk >> kw;
150 if (kw == "submesh" || kw == "texture_dir" || kw == "material_lib" || kw == "model") {
151 structured = true;
152 break;
153 }
154 if (kw == "newmtl") {
155 isMTL = true;
156 break;
157 }
158 }
159
160 if (isMTL) {
161 std::cout << ">> VKAbstractModel::loadModelTextures: MTL format detected, extracting map_Kd textures\n";
162 for (const auto &ln : lines) {
163 std::istringstream s(ln);
164 std::string kw;
165 s >> kw;
166 if (kw == "map_Kd") {
167 std::string texFile;
168 if (s >> texFile)
169 imagePaths.push_back(prefix + "/" + texFile);
170 }
171 }
172 } else if (structured) {
173 std::cout << ">> VKAbstractModel::loadModelTextures: structured .tex format detected\n";
174 for (const auto &ln : lines) {
175 std::istringstream s(ln);
176 std::string kw;
177 s >> kw;
178 if (kw == "texture") {
179 std::string texFile;
180 if (s >> texFile)
181 imagePaths.push_back(prefix + "/" + texFile);
182 }
183 // texture_dir, material_lib, model, submesh, end_submesh,
184 // diffuse, specular_power, smooth_group, size, material, object
185 // are parsed but only 'texture' produces an image path
186 }
187 } else {
188 // Plain format: one filename per line
189 for (const auto &ln : lines)
190 imagePaths.push_back(prefix + "/" + ln);
191 }
192
193 if (imagePaths.empty())
194 throw mx::Exception("No textures in .tex file");
195
196 VkFormat fmt = VK_FORMAT_R8G8B8A8_UNORM;
197 for (size_t i = 0; i < imagePaths.size(); ++i) {
198 SDL_Surface *img = png::LoadPNG(imagePaths[i].c_str());
199 if (!img) {
200 img = SDL_CreateRGBSurfaceWithFormat(0, 1, 1, 32, SDL_PIXELFORMAT_RGBA32);
201 if (img) { uint32_t *px = (uint32_t*)img->pixels; *px = 0xFFFFFFFF; }
202 else throw mx::Exception("Failed to create placeholder surface");
203 }
204
205 TexEntry tex;
206 tex.w = img->w;
207 tex.h = img->h;
208 VkDeviceSize imageSize = tex.w * tex.h * 4;
209
210 win->createImage(tex.w, tex.h, fmt, VK_IMAGE_TILING_OPTIMAL,
211 VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
212 VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, tex.image, tex.memory);
213
214 win->transitionImageLayout(tex.image, fmt, VK_IMAGE_LAYOUT_UNDEFINED,
215 VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
216
217 VkBuffer staging; VkDeviceMemory stagingMem;
218 win->createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
219 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
220 staging, stagingMem);
221 void *data;
222 vkMapMemory(win->getDevice(), stagingMem, 0, imageSize, 0, &data);
223 memcpy(data, img->pixels, (size_t)imageSize);
224 vkUnmapMemory(win->getDevice(), stagingMem);
225 win->copyBufferToImage(staging, tex.image, tex.w, tex.h);
226
227 win->transitionImageLayout(tex.image, fmt, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
228 VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
229
230 vkDestroyBuffer(win->getDevice(), staging, nullptr);
231 vkFreeMemory(win->getDevice(), stagingMem, nullptr);
232 SDL_FreeSurface(img);
233
234 tex.view = win->createImageView(tex.image, fmt, VK_IMAGE_ASPECT_COLOR_BIT);
235 modelTextures.push_back(tex);
236 std::cout << ">> VKAbstractModel::loadModelTextures: [" << i << "] " << imagePaths[i] << " (" << tex.w << "x" << tex.h << ")\n";
237 }
238 std::cout << ">> VKAbstractModel::loadModelTextures: " << modelTextures.size() << " texture(s) loaded [OK]\n";
239 }
240
241 void VKAbstractModel::loadModelTexturesFromMTL(VKWindow *win, const std::string &path) {
242 const auto &mats = obj.materials();
243 if (mats.empty()) {
244 std::cerr << ">> VKAbstractModel::loadModelTexturesFromMTL: no materials loaded\n";
245 return;
246 }
247 std::cout << ">> VKAbstractModel::loadModelTexturesFromMTL: loading " << mats.size() << " texture(s) from MTL map_Kd\n";
248 std::string prefix = path;
249 // Resolve mtl directory from the mtl file path
250 std::string mtlDir;
251 if (!obj.mtlLibPath().empty()) {
252 auto pos = obj.mtlLibPath().find_last_of("/\\");
253 if (pos != std::string::npos)
254 mtlDir = obj.mtlLibPath().substr(0, pos);
255 }
256
257 VkFormat fmt = VK_FORMAT_R8G8B8A8_UNORM;
258 for (size_t i = 0; i < mats.size(); ++i) {
259 std::string imgPath;
260 if (!mats[i].map_kd.empty()) {
261 // Try prefix first, then mtl directory
262 imgPath = prefix + "/" + mats[i].map_kd;
263 }
264
265 SDL_Surface *img = nullptr;
266 if (!imgPath.empty())
267 img = png::LoadPNG(imgPath.c_str());
268
269 if (!img) {
270 // Create a solid-colour placeholder from Kd
271 img = SDL_CreateRGBSurfaceWithFormat(0, 1, 1, 32, SDL_PIXELFORMAT_RGBA32);
272 if (img) {
273 uint8_t r = static_cast<uint8_t>(std::min(1.0f, mats[i].kd[0]) * 255.0f);
274 uint8_t g = static_cast<uint8_t>(std::min(1.0f, mats[i].kd[1]) * 255.0f);
275 uint8_t b = static_cast<uint8_t>(std::min(1.0f, mats[i].kd[2]) * 255.0f);
276 uint32_t *px = (uint32_t*)img->pixels;
277 *px = (0xFF << 24) | (b << 16) | (g << 8) | r;
278 } else {
279 throw mx::Exception("Failed to create placeholder surface for material " + mats[i].name);
280 }
281 }
282
283 TexEntry tex;
284 tex.w = img->w;
285 tex.h = img->h;
286 VkDeviceSize imageSize = tex.w * tex.h * 4;
287
288 win->createImage(tex.w, tex.h, fmt, VK_IMAGE_TILING_OPTIMAL,
289 VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
290 VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, tex.image, tex.memory);
291
292 win->transitionImageLayout(tex.image, fmt, VK_IMAGE_LAYOUT_UNDEFINED,
293 VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
294
295 VkBuffer staging; VkDeviceMemory stagingMem;
296 win->createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
297 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
298 staging, stagingMem);
299 void *data;
300 vkMapMemory(win->getDevice(), stagingMem, 0, imageSize, 0, &data);
301 memcpy(data, img->pixels, (size_t)imageSize);
302 vkUnmapMemory(win->getDevice(), stagingMem);
303 win->copyBufferToImage(staging, tex.image, tex.w, tex.h);
304
305 win->transitionImageLayout(tex.image, fmt, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
306 VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
307
308 vkDestroyBuffer(win->getDevice(), staging, nullptr);
309 vkFreeMemory(win->getDevice(), stagingMem, nullptr);
310 SDL_FreeSurface(img);
311
312 tex.view = win->createImageView(tex.image, fmt, VK_IMAGE_ASPECT_COLOR_BIT);
313 modelTextures.push_back(tex);
314 std::cout << ">> VKAbstractModel::loadModelTexturesFromMTL: [" << i << "] "
315 << mats[i].name << " -> " << (imgPath.empty() ? "(Kd placeholder)" : imgPath)
316 << " (" << tex.w << "x" << tex.h << ")\n";
317 }
318 std::cout << ">> VKAbstractModel::loadModelTexturesFromMTL: " << modelTextures.size() << " texture(s) loaded [OK]\n";
319 }
320
322 std::cout << ">> VKAbstractModel::resize: rebuilding UBOs and descriptors for new swapchain\n";
323 if (modelDescriptorPool != VK_NULL_HANDLE) {
324 vkDestroyDescriptorPool(win->getDevice(), modelDescriptorPool, nullptr);
325 modelDescriptorPool = VK_NULL_HANDLE;
326 }
327 modelDescriptorSets.clear();
328 for (size_t i = 0; i < modelUniformBuffers.size(); ++i) {
330 vkUnmapMemory(win->getDevice(), modelUniformBufferMemory[i]);
331 if (modelUniformBuffers[i] != VK_NULL_HANDLE) vkDestroyBuffer(win->getDevice(), modelUniformBuffers[i], nullptr);
332 if (modelUniformBufferMemory[i] != VK_NULL_HANDLE) vkFreeMemory(win->getDevice(), modelUniformBufferMemory[i], nullptr);
333 }
334 const size_t frameCount = win->swapChainImages.size();
335 modelUniformBuffers.assign(frameCount, VK_NULL_HANDLE);
336 modelUniformBufferMemory.assign(frameCount, VK_NULL_HANDLE);
337 modelUniformBuffersMapped.assign(frameCount, nullptr);
338 for (size_t i = 0; i < frameCount; ++i) {
340 VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
341 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
343 vkMapMemory(win->getDevice(), modelUniformBufferMemory[i], 0,
345 }
348 if (!vertShaderPath_.empty())
350 std::cout << ">> VKAbstractModel::resize: complete (" << win->swapChainImages.size() << " frames) [OK]\n";
351 }
352
354 std::cout << ">> VKAbstractModel::createModelDescriptorPool: creating pool...\n";
355 const uint32_t texCount = std::max<uint32_t>(1u, static_cast<uint32_t>(modelTextures.size()));
356 const uint32_t setCount = static_cast<uint32_t>(win->swapChainImages.size()) * texCount;
357
358 std::array<VkDescriptorPoolSize, 2> sizes{};
359 sizes[0] = {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, setCount};
360 sizes[1] = {VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, setCount};
361
362 VkDescriptorPoolCreateInfo ci{};
363 ci.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
364 ci.poolSizeCount = static_cast<uint32_t>(sizes.size());
365 ci.pPoolSizes = sizes.data();
366 ci.maxSets = setCount;
367 if (vkCreateDescriptorPool(win->getDevice(), &ci, nullptr, &modelDescriptorPool) != VK_SUCCESS)
368 throw mx::Exception("Failed to create model descriptor pool!");
369 std::cout << ">> VKAbstractModel::createModelDescriptorPool: maxSets=" << setCount << " [OK]\n";
370 }
372 std::cout << ">> VKAbstractModel::createModelDescriptorSets: allocating sets...\n";
373 const size_t texCount = std::max<size_t>(1, modelTextures.size());
374 const size_t setCount = win->swapChainImages.size() * texCount;
375
376 std::vector<VkDescriptorSetLayout> layouts(setCount, win->descriptorSetLayout);
377 VkDescriptorSetAllocateInfo ai{};
378 ai.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
379 ai.descriptorPool = modelDescriptorPool;
380 ai.descriptorSetCount = static_cast<uint32_t>(setCount);
381 ai.pSetLayouts = layouts.data();
382 modelDescriptorSets.resize(setCount);
383 if (vkAllocateDescriptorSets(win->getDevice(), &ai, modelDescriptorSets.data()) != VK_SUCCESS)
384 throw mx::Exception("Failed to allocate model descriptor sets!");
385
386 for (size_t frame = 0; frame < win->swapChainImages.size(); ++frame) {
387 VkDescriptorBufferInfo bufInfo{};
388 bufInfo.buffer = modelUniformBuffers[frame]; // use per-model UBO
389 bufInfo.offset = 0;
390 bufInfo.range = sizeof(mx::UniformBufferObject);
391
392 for (size_t tex = 0; tex < texCount; ++tex) {
393 size_t setIndex = frame * texCount + tex;
394
395 VkDescriptorImageInfo imgInfo{};
396 imgInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
397 imgInfo.imageView = modelTextures[tex].view;
398 imgInfo.sampler = win->textureSampler;
399
400 std::array<VkWriteDescriptorSet, 2> writes{};
401 writes[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
402 writes[0].dstSet = modelDescriptorSets[setIndex];
403 writes[0].dstBinding = 0;
404 writes[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
405 writes[0].descriptorCount = 1;
406 writes[0].pImageInfo = &imgInfo;
407
408 writes[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
409 writes[1].dstSet = modelDescriptorSets[setIndex];
410 writes[1].dstBinding = 1;
411 writes[1].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
412 writes[1].descriptorCount = 1;
413 writes[1].pBufferInfo = &bufInfo;
414
415 vkUpdateDescriptorSets(win->getDevice(), static_cast<uint32_t>(writes.size()),
416 writes.data(), 0, nullptr);
417 }
418 }
419 std::cout << ">> VKAbstractModel::createModelDescriptorSets: " << setCount << " set(s) allocated [OK]\n";
420 }
421
423 std::cout << ">> VKAbstractModel::cleanup: releasing model resources..."
424 << (isInstance_ ? " (instance)" : "") << "\n";
425 vkDeviceWaitIdle(win->getDevice());
426 if (isInstance_) {
428 if (modelPipeline != VK_NULL_HANDLE) {
429 vkDestroyPipeline(win->getDevice(), modelPipeline, nullptr);
430 modelPipeline = VK_NULL_HANDLE;
431 }
432 if (modelPipelineWireframe != VK_NULL_HANDLE) {
433 vkDestroyPipeline(win->getDevice(), modelPipelineWireframe, nullptr);
434 modelPipelineWireframe = VK_NULL_HANDLE;
435 }
436 std::cout << ">> VKAbstractModel::cleanup: instance resources released [OK]\n";
437 return;
438 }
439 if (modelPipeline != VK_NULL_HANDLE) {
440 vkDestroyPipeline(win->getDevice(), modelPipeline, nullptr);
441 modelPipeline = VK_NULL_HANDLE;
442 }
443 if (modelPipelineWireframe != VK_NULL_HANDLE) {
444 vkDestroyPipeline(win->getDevice(), modelPipelineWireframe, nullptr);
445 modelPipelineWireframe = VK_NULL_HANDLE;
446 }
447 obj.cleanup(win->getDevice());
448 for (size_t i = 0; i < modelUniformBuffers.size(); ++i) {
450 vkUnmapMemory(win->getDevice(), modelUniformBufferMemory[i]);
451 if (modelUniformBuffers[i] != VK_NULL_HANDLE) vkDestroyBuffer(win->getDevice(), modelUniformBuffers[i], nullptr);
452 if (modelUniformBufferMemory[i] != VK_NULL_HANDLE) vkFreeMemory(win->getDevice(), modelUniformBufferMemory[i], nullptr);
453 }
454 modelUniformBuffers.clear();
457 for (auto &t : modelTextures) {
458 if (t.view != VK_NULL_HANDLE) vkDestroyImageView(win->getDevice(), t.view, nullptr);
459 if (t.image != VK_NULL_HANDLE) vkDestroyImage(win->getDevice(), t.image, nullptr);
460 if (t.memory != VK_NULL_HANDLE) vkFreeMemory(win->getDevice(), t.memory, nullptr);
461 }
462 modelTextures.clear();
463 if (modelDescriptorPool != VK_NULL_HANDLE)
464 vkDestroyDescriptorPool(win->getDevice(), modelDescriptorPool, nullptr);
465 std::cout << ">> VKAbstractModel::cleanup: all resources released [OK]\n";
466 }
467
469 if (vertShaderPath_.empty() || fragShaderPath_.empty())
470 return;
471
472 if (modelPipeline != VK_NULL_HANDLE) {
473 vkDestroyPipeline(win->getDevice(), modelPipeline, nullptr);
474 modelPipeline = VK_NULL_HANDLE;
475 }
476 if (modelPipelineWireframe != VK_NULL_HANDLE) {
477 vkDestroyPipeline(win->getDevice(), modelPipelineWireframe, nullptr);
478 modelPipelineWireframe = VK_NULL_HANDLE;
479 }
480
481 std::cout << ">> VKAbstractModel::createModelPipeline: creating pipeline...\n";
482
483 auto vertCode = mx::readFile(vertShaderPath_);
484 auto fragCode = mx::readFile(fragShaderPath_);
485
486 VkShaderModule vertModule = win->createShaderModule(vertCode);
487 VkShaderModule fragModule = win->createShaderModule(fragCode);
488
489 VkPipelineShaderStageCreateInfo vertStage{};
490 vertStage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
491 vertStage.stage = VK_SHADER_STAGE_VERTEX_BIT;
492 vertStage.module = vertModule;
493 vertStage.pName = "main";
494
495 VkPipelineShaderStageCreateInfo fragStage{};
496 fragStage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
497 fragStage.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
498 fragStage.module = fragModule;
499 fragStage.pName = "main";
500
501 VkPipelineShaderStageCreateInfo stages[] = { vertStage, fragStage };
502
503 VkVertexInputBindingDescription binding{};
504 binding.binding = 0;
505 binding.stride = sizeof(Vertex);
506 binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
507
508 std::array<VkVertexInputAttributeDescription, 3> attrs{};
509 attrs[0].binding = 0; attrs[0].location = 0;
510 attrs[0].format = VK_FORMAT_R32G32B32_SFLOAT;
511 attrs[0].offset = offsetof(Vertex, pos);
512 attrs[1].binding = 0; attrs[1].location = 1;
513 attrs[1].format = VK_FORMAT_R32G32_SFLOAT;
514 attrs[1].offset = offsetof(Vertex, texCoord);
515 attrs[2].binding = 0; attrs[2].location = 2;
516 attrs[2].format = VK_FORMAT_R32G32B32_SFLOAT;
517 attrs[2].offset = offsetof(Vertex, normal);
518
519 VkPipelineVertexInputStateCreateInfo vertexInput{};
520 vertexInput.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
521 vertexInput.vertexBindingDescriptionCount = 1;
522 vertexInput.pVertexBindingDescriptions = &binding;
523 vertexInput.vertexAttributeDescriptionCount = static_cast<uint32_t>(attrs.size());
524 vertexInput.pVertexAttributeDescriptions = attrs.data();
525
526 VkPipelineInputAssemblyStateCreateInfo inputAssembly{};
527 inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
528 inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
529 inputAssembly.primitiveRestartEnable = VK_FALSE;
530
531 VkPipelineViewportStateCreateInfo viewportState{};
532 viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
533 viewportState.viewportCount = 1;
534 viewportState.scissorCount = 1;
535
536 std::array<VkDynamicState, 2> dynStates = {
537 VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR
538 };
539 VkPipelineDynamicStateCreateInfo dynState{};
540 dynState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
541 dynState.dynamicStateCount = static_cast<uint32_t>(dynStates.size());
542 dynState.pDynamicStates = dynStates.data();
543
544 VkPipelineRasterizationStateCreateInfo rasterizer{};
545 rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
546 rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
547 rasterizer.lineWidth = 1.0f;
548 rasterizer.cullMode = VK_CULL_MODE_NONE;
549 rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
550
551 VkPipelineMultisampleStateCreateInfo multisampling{};
552 multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
553 multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
554
555 VkPipelineDepthStencilStateCreateInfo depthStencil{};
556 depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
557 depthStencil.depthTestEnable = VK_TRUE;
558 depthStencil.depthWriteEnable = VK_TRUE;
559 depthStencil.depthCompareOp = VK_COMPARE_OP_LESS;
560
561 VkPipelineColorBlendAttachmentState colorBlendAttachment{};
562 colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
563 VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
564 colorBlendAttachment.blendEnable = VK_FALSE;
565
566 VkPipelineColorBlendStateCreateInfo colorBlending{};
567 colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
568 colorBlending.attachmentCount = 1;
569 colorBlending.pAttachments = &colorBlendAttachment;
570
571 VkGraphicsPipelineCreateInfo ci{};
572 ci.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
573 ci.stageCount = 2;
574 ci.pStages = stages;
575 ci.pVertexInputState = &vertexInput;
576 ci.pInputAssemblyState = &inputAssembly;
577 ci.pViewportState = &viewportState;
578 ci.pRasterizationState = &rasterizer;
579 ci.pMultisampleState = &multisampling;
580 ci.pDepthStencilState = &depthStencil;
581 ci.pColorBlendState = &colorBlending;
582 ci.pDynamicState = &dynState;
583 ci.layout = win->pipelineLayout;
584 ci.renderPass = win->renderPass;
585 ci.subpass = 0;
586
587 if (vkCreateGraphicsPipelines(win->getDevice(), VK_NULL_HANDLE, 1, &ci, nullptr, &modelPipeline) != VK_SUCCESS)
588 throw mx::Exception("VKAbstractModel: Failed to create model pipeline!");
589
590 rasterizer.polygonMode = VK_POLYGON_MODE_LINE;
591 if (vkCreateGraphicsPipelines(win->getDevice(), VK_NULL_HANDLE, 1, &ci, nullptr, &modelPipelineWireframe) != VK_SUCCESS) {
592 modelPipelineWireframe = VK_NULL_HANDLE;
593 }
594
595 vkDestroyShaderModule(win->getDevice(), fragModule, nullptr);
596 vkDestroyShaderModule(win->getDevice(), vertModule, nullptr);
597
598 std::cout << ">> VKAbstractModel::createModelPipeline: pipeline created [OK]\n";
599 }
600}
General-purpose exception class for the mx library.
Definition exception.hpp:20
std::vector< VkDescriptorSet > modelDescriptorSets
Descriptor sets (frames x textures).
std::vector< VkBuffer > modelUniformBuffers
Per-frame uniform buffers.
std::string fragShaderPath_
Fragment shader SPIR-V path.
bool isInstance_
True if geometry/textures are shared from another model.
void createModelPipeline(VKWindow *win)
Create a per-model graphics pipeline from stored shader paths.
void loadModelTexturesFromMTL(VKWindow *win, const std::string &path)
Load textures using map_Kd paths from parsed MTL materials.
void createModelDescriptorSets(VKWindow *win)
Allocate and write descriptor sets binding UBOs and textures.
std::vector< TexEntry > modelTextures
Per-sub-mesh texture entries.
void loadModelTextures(VKWindow *win, const std::string &tex, const std::string &path)
Load texture images listed in a .tex file and upload to GPU.
std::vector< void * > modelUniformBuffersMapped
Persistently mapped UBO pointers.
void createModelDescriptorPool(VKWindow *win)
Create the Vulkan descriptor pool sized for this model's textures.
glm::vec3 modelCenterOffset
Translation to centre the bounding box at origin.
void resize(VKWindow *win)
Recreate UBOs and descriptor sets after a swapchain resize.
void cleanup(VKWindow *win)
Destroy all Vulkan resources owned by this model.
void load(VKWindow *win, const std::string &model, const std::string &text, const std::string &path, float scale)
Load a model, its textures, and create all GPU resources.
VkPipeline modelPipelineWireframe
Per-model wireframe pipeline.
void loadInstance(VKWindow *win, VKAbstractModel &source)
Create a lightweight instance sharing geometry & textures from a source model.
std::string vertShaderPath_
Vertex shader SPIR-V path.
void render(int cmd)
Record rendering commands (currently a placeholder).
VkDescriptorPool modelDescriptorPool
Descriptor pool for this model.
std::vector< VkDeviceMemory > modelUniformBufferMemory
Memory for per-frame UBOs.
float modelRenderScale
Scale factor normalising model extent.
VKAbstractModel()=default
Default constructor.
void setShaders(VKWindow *win, const std::string &vert, const std::string &frag)
Set custom vertex and fragment shaders and create a per-model pipeline.
void cleanupInstanceResources(VKWindow *win)
Clean up only UBO and descriptor resources (used by instances).
void updateUBO(VKWindow *win, uint32_t imageIndex, const mx::UniformBufferObject &ubo)
Copy a UBO into the mapped buffer for a given frame.
VkPipeline modelPipeline
Per-model graphics pipeline (fill).
VkPhysicalDevice getPhysicalDevice() const
Definition vk.hpp:323
VkPipelineLayout pipelineLayout
Pipeline layout for the main pass.
Definition vk.hpp:354
void createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer &buffer, VkDeviceMemory &bufferMemory)
Allocate a Vulkan buffer with the given usage and memory properties.
Definition vk.cpp:872
VkQueue getGraphicsQueue() const
Definition vk.hpp:327
void transitionImageLayout(VkImage image, VkFormat format, VkImageLayout oldLayout, VkImageLayout newLayout)
Transition an image between two layouts using a pipeline barrier.
Definition vk.cpp:1026
VkShaderModule createShaderModule(const std::vector< char > &code)
Compile a SPIR-V shader module.
Definition vk.cpp:162
VkRenderPass renderPass
Main render pass.
Definition vk.hpp:353
VkCommandPool getCommandPool() const
Definition vk.hpp:329
std::vector< VkImage > swapChainImages
Swapchain image handles.
Definition vk.hpp:345
VkImageView createImageView(VkImage image, VkFormat format, VkImageAspectFlags aspectFlags)
Create an image view with specified format and aspect flags.
Definition vk.cpp:1184
void copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height)
Copy a staging buffer into a Vulkan image.
Definition vk.cpp:1117
VkSampler textureSampler
Sampler for the primary texture.
Definition vk.hpp:379
VkDevice getDevice() const
Definition vk.hpp:325
VkDescriptorSetLayout descriptorSetLayout
Main descriptor set layout.
Definition vk.hpp:342
void createImage(uint32_t width, uint32_t height, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, VkImage &image, VkDeviceMemory &imageMemory)
Create a Vulkan image with the specified parameters.
Definition vk.cpp:995
PNG image loading and saving utilities via SDL.
Definition cfg.cpp:11
std::vector< char > readFile(const std::string &filename)
Read an entire file into a byte vector.
Definition util.cpp:77
Definition object.hpp:15
SDL_Surface * LoadPNG(const char *file)
Load a PNG file into an SDL_Surface.
Definition loadpng.cpp:22
Standard UBO layout uploaded to shaders each frame.
Definition vk.hpp:102
Vulkan image, memory, and view for one loaded texture.
VkDeviceMemory memory
Device memory backing the image.
VkImageView view
Image view for shader sampling.
VkImage image
Vulkan image handle.
uint32_t h
Texture dimensions in pixels.
Per-vertex data sent to the Vulkan vertex buffer.
Definition model.cpp:34
High-level Vulkan 3-D model wrapper with texture and descriptor management.