14#include <glm/ext/matrix_clip_space.hpp>
15#include <glm/ext/matrix_transform.hpp>
34 glm::vec3
start{0.0f};
55 glm::vec3
scale{1.0f};
76 std::vector<TrailPoint>
trail{};
83 glm::vec3
color{1.0f, 0.5f, 0.2f};
92 glm::vec4
color{1.0f};
98 [[nodiscard]] glm::vec3
startPosition() const noexcept {
return startPositionValue; }
100 [[nodiscard]]
const std::vector<WallSegment> &
walls() const noexcept {
return wallSegments; }
102 [[nodiscard]]
const std::vector<PillarInstance> &
pillars() const noexcept {
return pillarInstances; }
104 [[nodiscard]] std::vector<Collectible> &
collectibles() noexcept {
return collectibleItems; }
106 [[nodiscard]]
const std::vector<Collectible> &
collectibles() const noexcept {
return collectibleItems; }
119 std::mt19937 rng(seed);
121 generatePillars(rng);
125 for (
int attempt = 0; attempt < 64; ++attempt) {
126 const glm::vec3 candidate =
randomPointInCell(startCellX, startCellZ, 0.6f, eyeHeight, rng, 0.5f);
128 startPositionValue = candidate;
133 generateCollectibles(rng);
137 const float halfThickness = wallThicknessValue * 0.5f;
138 const float hitRadius = radius + halfThickness;
140 const glm::vec3 segment = wall.end - wall.start;
141 const float segmentLength = glm::length(segment);
142 if (segmentLength < 0.0001f) {
146 const glm::vec3 segmentDir = segment / segmentLength;
147 const glm::vec3 toPoint = position - wall.start;
148 const float projection = glm::clamp(glm::dot(toPoint, segmentDir), 0.0f, segmentLength);
149 glm::vec3 closest = wall.start + (segmentDir * projection);
150 closest.y = position.y;
151 if (glm::length(position - closest) < hitRadius) {
160 const glm::vec2 player2d(position.x, position.z);
161 const glm::vec2 pillar2d(pillar.position.x, pillar.position.z);
162 if (glm::length(player2d - pillar2d) < (playerRadius + pillar.radius)) {
170 for (
size_t i = 0; i < collectibleItems.size(); ++i) {
171 if (!collectibleItems[i].
active) {
174 const Collectible &collectible = collectibleItems[i];
175 const glm::vec3 center = collectible.position + collectible.hitCenterOffset;
177 const glm::vec3 delta = point - center;
178 const float halfSide = collectible.radius;
179 if (std::abs(delta.x) <= halfSide &&
180 std::abs(delta.y) <= halfSide &&
181 std::abs(delta.z) <= halfSide) {
188 if (glm::length(center - point) < collectible.radius) {
196 [[nodiscard]] glm::vec3
randomPointInCell(
int cellX,
int cellZ,
float objectRadius,
float y, std::mt19937 &rng,
float margin)
const {
197 const float pad = objectRadius + wallThicknessValue * 0.5f + margin;
198 const float x0 = -size +
static_cast<float>(cellX) * cellSize;
199 const float z0 = -size +
static_cast<float>(cellZ) * cellSize;
200 const float x1 = x0 + cellSize;
201 const float z1 = z0 + cellSize;
203 float minX = x0 + pad;
204 float maxX = x1 - pad;
205 float minZ = z0 + pad;
206 float maxZ = z1 - pad;
208 minX = maxX = (x0 + x1) * 0.5f;
211 minZ = maxZ = (z0 + z1) * 0.5f;
214 std::uniform_real_distribution<float> distX(minX, maxX);
215 std::uniform_real_distribution<float> distZ(minZ, maxZ);
216 return glm::vec3(distX(rng), y, distZ(rng));
219 [[nodiscard]]
float wallThickness() const noexcept {
return wallThicknessValue; }
223 bool visited =
false;
224 std::array<bool, 4>
walls{
true,
true,
true,
true};
227 void generateMaze(std::mt19937 &rng) {
228 const int gridX = mazeGridX;
229 const int gridZ = mazeGridZ;
230 cellSize = (size * 2.0f) /
static_cast<float>(gridX);
232 auto indexFor = [gridX](
int x,
int z) {
233 return z * gridX + x;
236 std::vector<Cell> grid(
static_cast<size_t>(gridX * gridZ));
237 std::vector<std::pair<int, int>> stack;
238 stack.emplace_back(0, 0);
239 grid[
static_cast<size_t>(indexFor(0, 0))].visited =
true;
241 while (!stack.empty()) {
242 const auto [x, z] = stack.back();
243 std::vector<int> dirs;
244 if (z > 0 && !grid[
static_cast<size_t>(indexFor(x, z - 1))].visited) {
247 if (x < (gridX - 1) && !grid[
static_cast<size_t>(indexFor(x + 1, z))].visited) {
250 if (z < (gridZ - 1) && !grid[
static_cast<size_t>(indexFor(x, z + 1))].visited) {
253 if (x > 0 && !grid[
static_cast<size_t>(indexFor(x - 1, z))].visited) {
262 std::uniform_int_distribution<size_t> pick(0, dirs.size() - 1U);
263 const int d = dirs[pick(rng)];
276 grid[
static_cast<size_t>(indexFor(x, z))].
walls[
static_cast<size_t>(d)] =
false;
277 grid[
static_cast<size_t>(indexFor(nx, nz))].
walls[
static_cast<size_t>((d + 2) % 4)] =
false;
278 grid[
static_cast<size_t>(indexFor(nx, nz))].visited =
true;
279 stack.emplace_back(nx, nz);
282 wallSegments.clear();
283 for (
int z = 0; z < gridZ; ++z) {
284 for (
int x = 0; x < gridX; ++x) {
285 const float cx = -size +
static_cast<float>(x) * cellSize;
286 const float cz = -size +
static_cast<float>(z) * cellSize;
289 const float x1 = cx + cellSize;
290 const float z1 = cz + cellSize;
291 const Cell &cell = grid[
static_cast<size_t>(indexFor(x, z))];
294 wallSegments.push_back({glm::vec3(x0, 0.0f, z0), glm::vec3(x1, 0.0f, z0), wallHeight});
297 wallSegments.push_back({glm::vec3(x0, 0.0f, z1), glm::vec3(x0, 0.0f, z0), wallHeight});
299 if (x == (gridX - 1) && cell.walls[1]) {
300 wallSegments.push_back({glm::vec3(x1, 0.0f, z0), glm::vec3(x1, 0.0f, z1), wallHeight});
302 if (z == (gridZ - 1) && cell.walls[2]) {
303 wallSegments.push_back({glm::vec3(x1, 0.0f, z1), glm::vec3(x0, 0.0f, z1), wallHeight});
307 mergeContiguousWalls();
309 const float playerRadius = 0.5f;
312 startPositionValue =
randomPointInCell(0, 0, playerRadius, eyeHeight, rng, 0.5f);
314 for (
int z = 0; z < mazeGridZ; ++z) {
315 for (
int x = 0; x < mazeGridX; ++x) {
316 startPositionValue =
randomPointInCell(x, z, playerRadius, eyeHeight, rng, 0.5f);
327 void mergeContiguousWalls() {
328 if (wallSegments.empty()) {
332 struct NormalizedWall {
333 bool horizontal =
false;
334 float constantAxis = 0.0f;
335 float startAxis = 0.0f;
336 float endAxis = 0.0f;
340 constexpr float epsilon = 0.0001f;
341 constexpr float adjacencyEpsilon = 0.001f;
342 constexpr float quantizeScale = 1000.0f;
344 const auto quantize = [](
float value) {
345 return static_cast<int>(std::lround(value * quantizeScale));
348 std::vector<NormalizedWall> normalized;
349 normalized.reserve(wallSegments.size());
350 for (
const WallSegment &wall : wallSegments) {
351 const bool horizontal = std::abs(wall.start.z - wall.end.z) <= epsilon;
353 const float x0 = std::min(wall.start.x, wall.end.x);
354 const float x1 = std::max(wall.start.x, wall.end.x);
355 normalized.push_back({
true, wall.start.z, x0, x1, wall.height});
357 const float z0 = std::min(wall.start.z, wall.end.z);
358 const float z1 = std::max(wall.start.z, wall.end.z);
359 normalized.push_back({
false, wall.start.x, z0, z1, wall.height});
363 std::sort(normalized.begin(), normalized.end(), [&quantize](
const NormalizedWall &a,
const NormalizedWall &b) {
364 const auto keyA = std::array<int, 3>{a.horizontal ? 1 : 0, quantize(a.constantAxis), quantize(a.height)};
365 const auto keyB = std::array<int, 3>{b.horizontal ? 1 : 0, quantize(b.constantAxis), quantize(b.height)};
369 if (a.startAxis != b.startAxis) {
370 return a.startAxis < b.startAxis;
372 return a.endAxis < b.endAxis;
375 std::vector<WallSegment> merged;
376 merged.reserve(normalized.size());
378 while (index < normalized.size()) {
379 const NormalizedWall first = normalized[index];
380 float runStart = first.startAxis;
381 float runEnd = first.endAxis;
383 size_t next = index + 1;
384 while (next < normalized.size()) {
385 const NormalizedWall &candidate = normalized[next];
386 if (candidate.horizontal != first.horizontal || quantize(candidate.constantAxis) != quantize(first.constantAxis) || quantize(candidate.height) != quantize(first.height)) {
390 if (candidate.startAxis <= (runEnd + adjacencyEpsilon)) {
391 runEnd = std::max(runEnd, candidate.endAxis);
396 if (first.horizontal) {
397 merged.push_back({glm::vec3(runStart, 0.0f, first.constantAxis), glm::vec3(runEnd, 0.0f, first.constantAxis), first.height});
399 merged.push_back({glm::vec3(first.constantAxis, 0.0f, runStart), glm::vec3(first.constantAxis, 0.0f, runEnd), first.height});
401 runStart = candidate.startAxis;
402 runEnd = candidate.endAxis;
406 if (first.horizontal) {
407 merged.push_back({glm::vec3(runStart, 0.0f, first.constantAxis), glm::vec3(runEnd, 0.0f, first.constantAxis), first.height});
409 merged.push_back({glm::vec3(first.constantAxis, 0.0f, runStart), glm::vec3(first.constantAxis, 0.0f, runEnd), first.height});
414 wallSegments.swap(merged);
417 void generatePillars(std::mt19937 &rng) {
418 pillarInstances.clear();
419 std::uniform_real_distribution<float> radiusDist(0.5f, 1.5f);
420 std::uniform_real_distribution<float> heightDist(3.0f, 6.0f);
422 constexpr int targetPillars = 15;
423 constexpr int maxAttempts = targetPillars * 8;
425 for (
int attempt = 0; attempt < maxAttempts && created < targetPillars; ++attempt) {
426 const int cellX =
static_cast<int>(
rng() %
static_cast<uint32_t
>(mazeGridX));
427 const int cellZ =
static_cast<int>(
rng() %
static_cast<uint32_t
>(mazeGridZ));
429 if (cellX == startCellX && cellZ == startCellZ) {
433 pillar.radius = radiusDist(rng);
434 pillar.height = heightDist(rng);
435 pillar.position = randomPointInCell(cellX, cellZ, pillar.radius, 0.0f, rng, 0.3f);
436 if (!checkWallCollision(pillar.position, pillar.radius)) {
437 pillarInstances.push_back(pillar);
443 void generateCollectibles(std::mt19937 &rng) {
444 collectibleItems.clear();
445 const int usableCells = std::max(1, (mazeGridX * mazeGridZ) - 1);
446 const int targetCollectibles = usableCells * collectiblesPerCell;
447 collectibleItems.reserve(
static_cast<size_t>(targetCollectibles));
449 std::vector<Collectible::Type> types;
450 types.reserve(
static_cast<size_t>(targetCollectibles));
451 const int saturnCount = targetCollectibles / 2;
452 for (
int i = 0; i < saturnCount; ++i) {
455 for (
int i = saturnCount; i < targetCollectibles; ++i) {
458 std::shuffle(types.begin(), types.end(), rng);
460 std::uniform_real_distribution<float> saturnScale(0.4f, 0.8f);
461 std::uniform_real_distribution<float> saturnRotSpeed(5.0f, 15.0f);
462 std::uniform_real_distribution<float> birdScale(0.3f, 0.5f);
463 std::uniform_real_distribution<float> birdRotSpeed(20.0f, 60.0f);
466 for (
int cellZ = 0; cellZ < mazeGridZ; ++cellZ) {
467 for (
int cellX = 0; cellX < mazeGridX; ++cellX) {
468 if (cellX == startCellX && cellZ == startCellZ) {
471 for (
int slot = 0; slot < collectiblesPerCell; ++slot) {
472 if (typeIndex >= targetCollectibles) {
476 obj.type = types[
static_cast<size_t>(typeIndex)];
478 const float scale = saturnScale(rng);
479 obj.scale = glm::vec3(scale);
480 obj.rotationSpeed = saturnRotSpeed(rng);
481 obj.radius = 2.0f * scale;
483 const float scale = birdScale(rng);
484 obj.scale = glm::vec3(scale);
485 obj.rotationSpeed = birdRotSpeed(rng);
486 obj.radius = 0.5f * scale;
489 bool foundSpot =
false;
491 for (
int attempt = 0; attempt < 24 && !foundSpot; ++attempt) {
493 const float margin = (attempt < 18) ? 0.45f : 0.10f;
494 const glm::vec3 candidate = randomPointInCell(cellX, cellZ, obj.radius, y, rng, margin);
495 fallback = candidate;
497 bool overlapsOtherCollectible =
false;
498 for (
const Collectible &placed : collectibleItems) {
499 const float separation = std::max(5.0f, placed.radius + obj.radius + 0.2f);
500 if (glm::length(placed.position - candidate) < separation) {
501 overlapsOtherCollectible =
true;
506 if (!overlapsOtherCollectible && !checkWallCollision(candidate, obj.radius) && !checkPillarCollision(candidate, obj.radius)) {
507 obj.position = candidate;
514 obj.position = fallback;
516 collectibleItems.push_back(obj);
523 std::vector<WallSegment> wallSegments{};
524 std::vector<PillarInstance> pillarInstances{};
525 std::vector<Collectible> collectibleItems{};
528 float wallHeight = 5.0f;
529 float wallThicknessValue = 0.5f;
532 int collectiblesPerCell = 1;
533 float cellSize = 0.0f;
534 float eyeHeight = 1.7f;
537 glm::vec3 startPositionValue{0.0f, 1.7f, 0.0f};
540 class RawPillarRenderer {
542 struct PillarVertex {
543 glm::vec3 position{0.0f};
544 glm::vec2 texCoord{0.0f};
545 glm::vec3 normal{0.0f};
548 struct PillarUniforms {
549 glm::mat4 view{1.0f};
550 glm::mat4 proj{1.0f};
555 const std::string &textureManifestPath,
556 const std::string &textureBasePath,
557 const std::vector<char> &vertSpv,
558 const std::vector<char> &fragSpv) {
559 if (targetWindow ==
nullptr) {
560 throw mxvk::Exception(
"walk: raw pillar renderer requires a valid window");
562 window = targetWindow;
564 fragmentSpv = fragSpv;
566 if (!window->ensureRenderResources()) {
567 throw mxvk::Exception(
"walk: raw pillar renderer requires render resources");
571 loadTexture(textureManifestPath, textureBasePath);
572 createTextureSampler();
573 createDescriptorSetLayout();
574 createUniformBuffers();
575 createDescriptorPool();
576 createDescriptorSets();
581 if (targetWindow ==
nullptr || targetWindow->
getDevice() == VK_NULL_HANDLE) {
585 window = targetWindow;
587 destroyDescriptors();
588 createDescriptorSetLayout();
589 createUniformBuffers();
590 createDescriptorPool();
591 createDescriptorSets();
598 if (window ==
nullptr || window->getDevice() == VK_NULL_HANDLE || newFragSpv.empty()) {
601 vkDeviceWaitIdle(window->getDevice());
602 fragmentSpv = newFragSpv;
608 if (targetWindow ==
nullptr || targetWindow->
getDevice() == VK_NULL_HANDLE) {
612 window = targetWindow;
614 destroyDescriptors();
622 const std::vector<PillarInstance> &pillars,
623 const glm::mat4 &view,
624 const glm::mat4 &proj,
625 const glm::vec4 &fx) {
626 if (cmd == VK_NULL_HANDLE || pipeline == VK_NULL_HANDLE || pipelineLayout == VK_NULL_HANDLE) {
629 if (imageIndex >= uniformBuffersMapped.size() || descriptorSets.empty() || vertexBuffer == VK_NULL_HANDLE || indexBuffer == VK_NULL_HANDLE) {
634 uniforms.view = view;
635 uniforms.proj = proj;
637 std::memcpy(uniformBuffersMapped[imageIndex], &uniforms,
sizeof(
PillarUniforms));
639 vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
640 vkCmdBindDescriptorSets(cmd,
641 VK_PIPELINE_BIND_POINT_GRAPHICS,
645 &descriptorSets[imageIndex],
649 const VkDeviceSize offset = 0;
650 vkCmdBindVertexBuffers(cmd, 0, 1, &vertexBuffer, &offset);
651 vkCmdBindIndexBuffer(cmd, indexBuffer, 0, VK_INDEX_TYPE_UINT32);
657 constexpr float baseSink = 0.02f;
658 glm::mat4 model = glm::translate(glm::mat4(1.0f), glm::vec3(pillar.position.x, pillar.position.y - baseSink, pillar.position.z));
659 model = glm::scale(model, glm::vec3(pillar.radius, pillar.height, pillar.radius));
660 vkCmdPushConstants(cmd, pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0,
sizeof(glm::mat4), &model);
661 vkCmdDrawIndexed(cmd, indexCount, 1, 0, 0, 0);
666 [[nodiscard]] uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties)
const {
667 VkPhysicalDeviceMemoryProperties memProperties{};
668 vkGetPhysicalDeviceMemoryProperties(window->getPhysicalDevice(), &memProperties);
669 for (uint32_t i = 0; i < memProperties.memoryTypeCount; ++i) {
670 if ((typeFilter & (1u << i)) != 0u && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) {
674 throw mxvk::Exception(
"walk: failed to find suitable memory type for raw pillar renderer");
677 void createBuffer(VkDeviceSize size,
678 VkBufferUsageFlags usage,
679 VkMemoryPropertyFlags properties,
681 VkDeviceMemory &bufferMemory)
const {
682 VkBufferCreateInfo bufferInfo{};
683 bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
684 bufferInfo.size = size;
685 bufferInfo.usage = usage;
686 bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
688 if (vkCreateBuffer(window->getDevice(), &bufferInfo,
nullptr, &buffer) != VK_SUCCESS) {
689 throw mxvk::Exception(
"walk: failed to create raw pillar buffer");
692 VkMemoryRequirements requirements{};
693 vkGetBufferMemoryRequirements(window->getDevice(), buffer, &requirements);
695 VkMemoryAllocateInfo allocInfo{};
696 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
697 allocInfo.allocationSize = requirements.size;
700 allocInfo.memoryTypeIndex = findMemoryType(requirements.memoryTypeBits, properties);
701 if (vkAllocateMemory(window->getDevice(), &allocInfo,
nullptr, &bufferMemory) != VK_SUCCESS) {
702 throw mxvk::Exception(
"walk: failed to allocate raw pillar buffer memory");
705 if (vkBindBufferMemory(window->getDevice(), buffer, bufferMemory, 0) != VK_SUCCESS) {
706 throw mxvk::Exception(
"walk: failed to bind raw pillar buffer memory");
709 if (bufferMemory != VK_NULL_HANDLE) {
710 vkFreeMemory(window->getDevice(), bufferMemory,
nullptr);
711 bufferMemory = VK_NULL_HANDLE;
713 if (buffer != VK_NULL_HANDLE) {
714 vkDestroyBuffer(window->getDevice(), buffer,
nullptr);
715 buffer = VK_NULL_HANDLE;
721 void createImage(uint32_t width,
724 VkImageTiling tiling,
725 VkImageUsageFlags usage,
726 VkMemoryPropertyFlags properties,
728 VkDeviceMemory &memory)
const {
729 VkImageCreateInfo imageInfo{};
730 imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
731 imageInfo.imageType = VK_IMAGE_TYPE_2D;
732 imageInfo.extent.width = width;
733 imageInfo.extent.height = height;
734 imageInfo.extent.depth = 1;
735 imageInfo.mipLevels = 1;
736 imageInfo.arrayLayers = 1;
737 imageInfo.format = format;
738 imageInfo.tiling = tiling;
739 imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
740 imageInfo.usage = usage;
741 imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
742 imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
744 if (vkCreateImage(window->getDevice(), &imageInfo,
nullptr, &image) != VK_SUCCESS) {
745 throw mxvk::Exception(
"walk: failed to create raw pillar image");
748 VkMemoryRequirements requirements{};
749 vkGetImageMemoryRequirements(window->getDevice(), image, &requirements);
751 VkMemoryAllocateInfo allocInfo{};
752 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
753 allocInfo.allocationSize = requirements.size;
756 allocInfo.memoryTypeIndex = findMemoryType(requirements.memoryTypeBits, properties);
757 if (vkAllocateMemory(window->getDevice(), &allocInfo,
nullptr, &memory) != VK_SUCCESS) {
758 throw mxvk::Exception(
"walk: failed to allocate raw pillar image memory");
761 if (vkBindImageMemory(window->getDevice(), image, memory, 0) != VK_SUCCESS) {
762 throw mxvk::Exception(
"walk: failed to bind raw pillar image memory");
765 if (memory != VK_NULL_HANDLE) {
766 vkFreeMemory(window->getDevice(), memory,
nullptr);
767 memory = VK_NULL_HANDLE;
769 if (image != VK_NULL_HANDLE) {
770 vkDestroyImage(window->getDevice(), image,
nullptr);
771 image = VK_NULL_HANDLE;
777 VkImageView createImageView(VkImage image, VkFormat format, VkImageAspectFlags aspectFlags)
const {
778 VkImageViewCreateInfo viewInfo{};
779 viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
780 viewInfo.image = image;
781 viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
782 viewInfo.format = format;
783 viewInfo.subresourceRange.aspectMask = aspectFlags;
784 viewInfo.subresourceRange.baseMipLevel = 0;
785 viewInfo.subresourceRange.levelCount = 1;
786 viewInfo.subresourceRange.baseArrayLayer = 0;
787 viewInfo.subresourceRange.layerCount = 1;
789 VkImageView imageView = VK_NULL_HANDLE;
790 if (vkCreateImageView(window->getDevice(), &viewInfo,
nullptr, &imageView) != VK_SUCCESS) {
791 throw mxvk::Exception(
"walk: failed to create raw pillar image view");
796 [[nodiscard]] VkCommandBuffer beginSingleTimeCommands()
const {
797 VkCommandBufferAllocateInfo allocInfo{};
798 allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
799 allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
800 allocInfo.commandPool = window->getCommandPool();
801 allocInfo.commandBufferCount = 1;
803 VkCommandBuffer commandBuffer = VK_NULL_HANDLE;
804 if (vkAllocateCommandBuffers(window->getDevice(), &allocInfo, &commandBuffer) != VK_SUCCESS) {
805 throw mxvk::Exception(
"walk: failed to allocate raw pillar command buffer");
808 VkCommandBufferBeginInfo beginInfo{};
809 beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
810 beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
811 if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) {
812 vkFreeCommandBuffers(window->getDevice(), window->getCommandPool(), 1, &commandBuffer);
813 throw mxvk::Exception(
"walk: failed to begin raw pillar command buffer");
816 return commandBuffer;
819 void endSingleTimeCommands(VkCommandBuffer commandBuffer)
const {
820 if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) {
821 vkFreeCommandBuffers(window->getDevice(), window->getCommandPool(), 1, &commandBuffer);
822 throw mxvk::Exception(
"walk: failed to end raw pillar command buffer");
825 VkSubmitInfo submitInfo{};
826 submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
827 submitInfo.commandBufferCount = 1;
828 submitInfo.pCommandBuffers = &commandBuffer;
830 if (vkQueueSubmit(window->getGraphicsQueue(), 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS) {
831 vkFreeCommandBuffers(window->getDevice(), window->getCommandPool(), 1, &commandBuffer);
832 throw mxvk::Exception(
"walk: failed to submit raw pillar command buffer");
834 if (vkQueueWaitIdle(window->getGraphicsQueue()) != VK_SUCCESS) {
835 vkFreeCommandBuffers(window->getDevice(), window->getCommandPool(), 1, &commandBuffer);
836 throw mxvk::Exception(
"walk: failed to wait for raw pillar upload queue");
839 vkFreeCommandBuffers(window->getDevice(), window->getCommandPool(), 1, &commandBuffer);
842 void transitionImageLayout(VkImage image, VkImageLayout oldLayout, VkImageLayout newLayout)
const {
843 VkCommandBuffer cmd = beginSingleTimeCommands();
845 VkImageMemoryBarrier barrier{};
846 barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
847 barrier.oldLayout = oldLayout;
848 barrier.newLayout = newLayout;
849 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
850 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
851 barrier.image = image;
852 barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
853 barrier.subresourceRange.baseMipLevel = 0;
854 barrier.subresourceRange.levelCount = 1;
855 barrier.subresourceRange.baseArrayLayer = 0;
856 barrier.subresourceRange.layerCount = 1;
858 VkPipelineStageFlags sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
859 VkPipelineStageFlags destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
860 if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
861 barrier.srcAccessMask = 0;
862 barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
863 }
else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) {
864 barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
865 barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
866 sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
867 destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
870 vkCmdPipelineBarrier(cmd, sourceStage, destinationStage, 0, 0,
nullptr, 0,
nullptr, 1, &barrier);
871 endSingleTimeCommands(cmd);
874 void copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height)
const {
875 VkCommandBuffer cmd = beginSingleTimeCommands();
876 VkBufferImageCopy region{};
877 region.bufferOffset = 0;
878 region.bufferRowLength = 0;
879 region.bufferImageHeight = 0;
880 region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
881 region.imageSubresource.mipLevel = 0;
882 region.imageSubresource.baseArrayLayer = 0;
883 region.imageSubresource.layerCount = 1;
884 region.imageOffset = {0, 0, 0};
885 region.imageExtent = {width, height, 1};
887 vkCmdCopyBufferToImage(cmd, buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion);
888 endSingleTimeCommands(cmd);
891 void createTextureSampler() {
892 if (textureSampler != VK_NULL_HANDLE) {
896 VkPhysicalDeviceFeatures deviceFeatures{};
897 vkGetPhysicalDeviceFeatures(window->getPhysicalDevice(), &deviceFeatures);
898 VkPhysicalDeviceProperties deviceProperties{};
899 vkGetPhysicalDeviceProperties(window->getPhysicalDevice(), &deviceProperties);
900 const bool anisotropySupported = deviceFeatures.samplerAnisotropy == VK_TRUE;
901 const float anisotropyLevel = anisotropySupported
902 ? std::min(8.0f, deviceProperties.limits.maxSamplerAnisotropy)
905 VkSamplerCreateInfo samplerInfo{};
906 samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
907 samplerInfo.magFilter = VK_FILTER_LINEAR;
908 samplerInfo.minFilter = VK_FILTER_LINEAR;
909 samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;
910 samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT;
911 samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
912 samplerInfo.anisotropyEnable = anisotropySupported ? VK_TRUE : VK_FALSE;
913 samplerInfo.maxAnisotropy = anisotropyLevel;
914 samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK;
915 samplerInfo.unnormalizedCoordinates = VK_FALSE;
916 samplerInfo.compareEnable = VK_FALSE;
917 samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS;
918 samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
920 if (vkCreateSampler(window->getDevice(), &samplerInfo,
nullptr, &textureSampler) != VK_SUCCESS) {
921 throw mxvk::Exception(
"walk: failed to create raw pillar texture sampler");
925 void createDescriptorSetLayout() {
926 if (descriptorSetLayout != VK_NULL_HANDLE) {
930 VkDescriptorSetLayoutBinding samplerBinding{};
931 samplerBinding.binding = 0;
932 samplerBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
933 samplerBinding.descriptorCount = 1;
934 samplerBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
936 VkDescriptorSetLayoutBinding uboBinding{};
937 uboBinding.binding = 1;
938 uboBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
939 uboBinding.descriptorCount = 1;
940 uboBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
942 const std::array<VkDescriptorSetLayoutBinding, 2> bindings = {samplerBinding, uboBinding};
944 VkDescriptorSetLayoutCreateInfo layoutInfo{};
945 layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
946 layoutInfo.bindingCount =
static_cast<uint32_t
>(bindings.size());
947 layoutInfo.pBindings = bindings.data();
949 if (vkCreateDescriptorSetLayout(window->getDevice(), &layoutInfo,
nullptr, &descriptorSetLayout) != VK_SUCCESS) {
950 throw mxvk::Exception(
"walk: failed to create raw pillar descriptor set layout");
954 void createUniformBuffers() {
955 destroyUniformBuffers();
957 const size_t frameCount = window->getSwapchainImageCount();
958 if (frameCount == 0) {
962 uniformBuffers.resize(frameCount, VK_NULL_HANDLE);
963 uniformBufferMemory.resize(frameCount, VK_NULL_HANDLE);
964 uniformBuffersMapped.resize(frameCount,
nullptr);
966 for (
size_t i = 0; i < frameCount; ++i) {
967 createBuffer(
sizeof(PillarUniforms),
968 VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
969 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
971 uniformBufferMemory[i]);
972 vkMapMemory(window->getDevice(), uniformBufferMemory[i], 0,
sizeof(PillarUniforms), 0, &uniformBuffersMapped[i]);
976 void createDescriptorPool() {
977 const uint32_t frameCount =
static_cast<uint32_t
>(window->getSwapchainImageCount());
978 std::array<VkDescriptorPoolSize, 2> poolSizes{};
979 poolSizes[0].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
980 poolSizes[0].descriptorCount = frameCount;
981 poolSizes[1].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
982 poolSizes[1].descriptorCount = frameCount;
984 VkDescriptorPoolCreateInfo poolInfo{};
985 poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
986 poolInfo.poolSizeCount =
static_cast<uint32_t
>(poolSizes.size());
987 poolInfo.pPoolSizes = poolSizes.data();
988 poolInfo.maxSets = frameCount;
990 if (vkCreateDescriptorPool(window->getDevice(), &poolInfo,
nullptr, &descriptorPool) != VK_SUCCESS) {
991 throw mxvk::Exception(
"walk: failed to create raw pillar descriptor pool");
995 void createDescriptorSets() {
996 const size_t frameCount = window->getSwapchainImageCount();
997 std::vector<VkDescriptorSetLayout> layouts(frameCount, descriptorSetLayout);
999 VkDescriptorSetAllocateInfo allocInfo{};
1000 allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
1001 allocInfo.descriptorPool = descriptorPool;
1002 allocInfo.descriptorSetCount =
static_cast<uint32_t
>(frameCount);
1003 allocInfo.pSetLayouts = layouts.data();
1005 descriptorSets.resize(frameCount, VK_NULL_HANDLE);
1006 if (vkAllocateDescriptorSets(window->getDevice(), &allocInfo, descriptorSets.data()) != VK_SUCCESS) {
1007 throw mxvk::Exception(
"walk: failed to allocate raw pillar descriptor sets");
1010 VkDescriptorImageInfo imageInfo{};
1011 imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
1012 imageInfo.imageView = textureView;
1013 imageInfo.sampler = textureSampler;
1015 for (
size_t i = 0; i < frameCount; ++i) {
1016 VkDescriptorBufferInfo bufferInfo{};
1017 bufferInfo.buffer = uniformBuffers[i];
1018 bufferInfo.offset = 0;
1019 bufferInfo.range =
sizeof(PillarUniforms);
1021 std::array<VkWriteDescriptorSet, 2> writes{};
1022 writes[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
1023 writes[0].dstSet = descriptorSets[i];
1024 writes[0].dstBinding = 0;
1025 writes[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
1026 writes[0].descriptorCount = 1;
1027 writes[0].pImageInfo = &imageInfo;
1029 writes[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
1030 writes[1].dstSet = descriptorSets[i];
1031 writes[1].dstBinding = 1;
1032 writes[1].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
1033 writes[1].descriptorCount = 1;
1034 writes[1].pBufferInfo = &bufferInfo;
1036 vkUpdateDescriptorSets(window->getDevice(),
static_cast<uint32_t
>(writes.size()), writes.data(), 0,
nullptr);
1040 void createPipeline() {
1041 if (descriptorSetLayout == VK_NULL_HANDLE || vertexSpv.empty() || fragmentSpv.empty() || window->getSwapchainFormat() == VK_FORMAT_UNDEFINED) {
1048 VkPipelineShaderStageCreateInfo vertStage{};
1049 vertStage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
1050 vertStage.stage = VK_SHADER_STAGE_VERTEX_BIT;
1051 vertStage.module = vertModule;
1052 vertStage.pName =
"main";
1054 VkPipelineShaderStageCreateInfo fragStage{};
1055 fragStage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
1056 fragStage.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
1057 fragStage.module = fragModule;
1058 fragStage.pName =
"main";
1059 const std::array<VkPipelineShaderStageCreateInfo, 2> stages = {vertStage, fragStage};
1061 VkVertexInputBindingDescription binding{};
1062 binding.binding = 0;
1063 binding.stride =
sizeof(PillarVertex);
1064 binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
1066 std::array<VkVertexInputAttributeDescription, 3> attrs{};
1067 attrs[0] = {0, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(PillarVertex, position)};
1068 attrs[1] = {1, 0, VK_FORMAT_R32G32_SFLOAT, offsetof(PillarVertex, texCoord)};
1069 attrs[2] = {2, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(PillarVertex, normal)};
1071 VkPipelineVertexInputStateCreateInfo vertexInput{};
1072 vertexInput.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
1073 vertexInput.vertexBindingDescriptionCount = 1;
1074 vertexInput.pVertexBindingDescriptions = &binding;
1075 vertexInput.vertexAttributeDescriptionCount =
static_cast<uint32_t
>(attrs.size());
1076 vertexInput.pVertexAttributeDescriptions = attrs.data();
1078 VkPipelineInputAssemblyStateCreateInfo inputAssembly{};
1079 inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
1080 inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
1082 const std::array<VkDynamicState, 2> dynamicStates = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
1083 VkPipelineDynamicStateCreateInfo dynamicInfo{};
1084 dynamicInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
1085 dynamicInfo.dynamicStateCount =
static_cast<uint32_t
>(dynamicStates.size());
1086 dynamicInfo.pDynamicStates = dynamicStates.data();
1088 VkPipelineViewportStateCreateInfo viewportState{};
1089 viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
1090 viewportState.viewportCount = 1;
1091 viewportState.scissorCount = 1;
1093 VkPipelineRasterizationStateCreateInfo rasterizer{};
1094 rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
1095 rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
1098 rasterizer.cullMode = VK_CULL_MODE_NONE;
1099 rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE;
1100 rasterizer.lineWidth = 1.0f;
1101 rasterizer.depthBiasEnable = VK_FALSE;
1103 VkPipelineMultisampleStateCreateInfo multisample{};
1104 multisample.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
1105 multisample.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
1107 VkPipelineDepthStencilStateCreateInfo depthStencil{};
1108 depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
1109 depthStencil.depthTestEnable = VK_TRUE;
1110 depthStencil.depthWriteEnable = VK_TRUE;
1111 depthStencil.depthCompareOp = VK_COMPARE_OP_LESS;
1113 VkPipelineColorBlendAttachmentState blendAttachment{};
1114 blendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
1115 blendAttachment.blendEnable = VK_FALSE;
1117 VkPipelineColorBlendStateCreateInfo colorBlend{};
1118 colorBlend.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
1119 colorBlend.attachmentCount = 1;
1120 colorBlend.pAttachments = &blendAttachment;
1122 VkPushConstantRange pushRange{};
1123 pushRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
1124 pushRange.offset = 0;
1125 pushRange.size =
sizeof(glm::mat4);
1127 VkPipelineLayoutCreateInfo layoutInfo{};
1128 layoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
1129 layoutInfo.setLayoutCount = 1;
1130 layoutInfo.pSetLayouts = &descriptorSetLayout;
1131 layoutInfo.pushConstantRangeCount = 1;
1132 layoutInfo.pPushConstantRanges = &pushRange;
1134 if (vkCreatePipelineLayout(window->getDevice(), &layoutInfo,
nullptr, &pipelineLayout) != VK_SUCCESS) {
1135 vkDestroyShaderModule(window->getDevice(), fragModule,
nullptr);
1136 vkDestroyShaderModule(window->getDevice(), vertModule,
nullptr);
1137 throw mxvk::Exception(
"walk: failed to create raw pillar pipeline layout");
1140 const VkFormat colorFormat = window->getSwapchainFormat();
1141 const VkFormat depthFormat = window->getDepthFormat();
1142 VkPipelineRenderingCreateInfo renderingInfo{};
1143 renderingInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO;
1144 renderingInfo.colorAttachmentCount = 1;
1145 renderingInfo.pColorAttachmentFormats = &colorFormat;
1146 if (depthFormat != VK_FORMAT_UNDEFINED) {
1147 renderingInfo.depthAttachmentFormat = depthFormat;
1150 VkGraphicsPipelineCreateInfo pipelineInfo{};
1151 pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
1152 pipelineInfo.pNext = &renderingInfo;
1153 pipelineInfo.stageCount =
static_cast<uint32_t
>(stages.size());
1154 pipelineInfo.pStages = stages.data();
1155 pipelineInfo.pVertexInputState = &vertexInput;
1156 pipelineInfo.pInputAssemblyState = &inputAssembly;
1157 pipelineInfo.pViewportState = &viewportState;
1158 pipelineInfo.pRasterizationState = &rasterizer;
1159 pipelineInfo.pMultisampleState = &multisample;
1160 pipelineInfo.pDepthStencilState = &depthStencil;
1161 pipelineInfo.pColorBlendState = &colorBlend;
1162 pipelineInfo.pDynamicState = &dynamicInfo;
1163 pipelineInfo.layout = pipelineLayout;
1164 pipelineInfo.renderPass = VK_NULL_HANDLE;
1166 if (vkCreateGraphicsPipelines(window->getDevice(), VK_NULL_HANDLE, 1, &pipelineInfo,
nullptr, &pipeline) != VK_SUCCESS) {
1167 vkDestroyPipelineLayout(window->getDevice(), pipelineLayout,
nullptr);
1168 pipelineLayout = VK_NULL_HANDLE;
1169 vkDestroyShaderModule(window->getDevice(), fragModule,
nullptr);
1170 vkDestroyShaderModule(window->getDevice(), vertModule,
nullptr);
1171 throw mxvk::Exception(
"walk: failed to create raw pillar graphics pipeline");
1174 vkDestroyShaderModule(window->getDevice(), fragModule,
nullptr);
1175 vkDestroyShaderModule(window->getDevice(), vertModule,
nullptr);
1178 void destroyPipeline() {
1179 if (window ==
nullptr || window->getDevice() == VK_NULL_HANDLE) {
1180 pipeline = VK_NULL_HANDLE;
1181 pipelineLayout = VK_NULL_HANDLE;
1185 if (pipeline != VK_NULL_HANDLE) {
1186 vkDestroyPipeline(window->getDevice(), pipeline,
nullptr);
1187 pipeline = VK_NULL_HANDLE;
1189 if (pipelineLayout != VK_NULL_HANDLE) {
1190 vkDestroyPipelineLayout(window->getDevice(), pipelineLayout,
nullptr);
1191 pipelineLayout = VK_NULL_HANDLE;
1195 void destroyDescriptors() {
1196 if (window ==
nullptr || window->getDevice() == VK_NULL_HANDLE) {
1197 descriptorSets.clear();
1198 descriptorPool = VK_NULL_HANDLE;
1199 descriptorSetLayout = VK_NULL_HANDLE;
1200 destroyUniformBuffers();
1204 descriptorSets.clear();
1205 if (descriptorPool != VK_NULL_HANDLE) {
1206 vkDestroyDescriptorPool(window->getDevice(), descriptorPool,
nullptr);
1207 descriptorPool = VK_NULL_HANDLE;
1209 if (descriptorSetLayout != VK_NULL_HANDLE) {
1210 vkDestroyDescriptorSetLayout(window->getDevice(), descriptorSetLayout,
nullptr);
1211 descriptorSetLayout = VK_NULL_HANDLE;
1213 destroyUniformBuffers();
1216 void destroyUniformBuffers() {
1217 if (window ==
nullptr || window->getDevice() == VK_NULL_HANDLE) {
1218 uniformBuffers.clear();
1219 uniformBufferMemory.clear();
1220 uniformBuffersMapped.clear();
1224 for (
size_t i = 0; i < uniformBuffers.size(); ++i) {
1225 if (uniformBuffersMapped[i] !=
nullptr) {
1226 vkUnmapMemory(window->getDevice(), uniformBufferMemory[i]);
1227 uniformBuffersMapped[i] =
nullptr;
1229 if (uniformBuffers[i] != VK_NULL_HANDLE) {
1230 vkDestroyBuffer(window->getDevice(), uniformBuffers[i],
nullptr);
1232 if (uniformBufferMemory[i] != VK_NULL_HANDLE) {
1233 vkFreeMemory(window->getDevice(), uniformBufferMemory[i],
nullptr);
1237 uniformBuffers.clear();
1238 uniformBufferMemory.clear();
1239 uniformBuffersMapped.clear();
1242 void destroyTexture() {
1243 if (window ==
nullptr || window->getDevice() == VK_NULL_HANDLE) {
1244 textureView = VK_NULL_HANDLE;
1245 textureImage = VK_NULL_HANDLE;
1246 textureMemory = VK_NULL_HANDLE;
1247 textureSampler = VK_NULL_HANDLE;
1251 if (textureView != VK_NULL_HANDLE) {
1252 vkDestroyImageView(window->getDevice(), textureView,
nullptr);
1253 textureView = VK_NULL_HANDLE;
1255 if (textureImage != VK_NULL_HANDLE) {
1256 vkDestroyImage(window->getDevice(), textureImage,
nullptr);
1257 textureImage = VK_NULL_HANDLE;
1259 if (textureMemory != VK_NULL_HANDLE) {
1260 vkFreeMemory(window->getDevice(), textureMemory,
nullptr);
1261 textureMemory = VK_NULL_HANDLE;
1263 if (textureSampler != VK_NULL_HANDLE) {
1264 vkDestroySampler(window->getDevice(), textureSampler,
nullptr);
1265 textureSampler = VK_NULL_HANDLE;
1269 void destroyBuffers() {
1270 if (window ==
nullptr || window->getDevice() == VK_NULL_HANDLE) {
1271 vertexBuffer = VK_NULL_HANDLE;
1272 vertexMemory = VK_NULL_HANDLE;
1273 indexBuffer = VK_NULL_HANDLE;
1274 indexMemory = VK_NULL_HANDLE;
1278 if (vertexBuffer != VK_NULL_HANDLE) {
1279 vkDestroyBuffer(window->getDevice(), vertexBuffer,
nullptr);
1280 vertexBuffer = VK_NULL_HANDLE;
1282 if (vertexMemory != VK_NULL_HANDLE) {
1283 vkFreeMemory(window->getDevice(), vertexMemory,
nullptr);
1284 vertexMemory = VK_NULL_HANDLE;
1286 if (indexBuffer != VK_NULL_HANDLE) {
1287 vkDestroyBuffer(window->getDevice(), indexBuffer,
nullptr);
1288 indexBuffer = VK_NULL_HANDLE;
1290 if (indexMemory != VK_NULL_HANDLE) {
1291 vkFreeMemory(window->getDevice(), indexMemory,
nullptr);
1292 indexMemory = VK_NULL_HANDLE;
1296 void buildGeometry() {
1297 constexpr int segments = 16;
1298 constexpr float bottomCapScale = 1.5f;
1299 constexpr float baseDepth = -0.05f;
1301 std::vector<float> vertices;
1302 std::vector<uint32_t> indices;
1303 vertices.reserve(128 * 8);
1304 indices.reserve(192);
1306 for (
int i = 0; i <= segments; ++i) {
1307 const float angle =
static_cast<float>(i) /
static_cast<float>(segments) * 2.0f * 3.14159265358979323846f;
1308 const float xBottom = std::cos(angle) * bottomCapScale;
1309 const float zBottom = std::sin(angle) * bottomCapScale;
1310 const float xTop = std::cos(angle);
1311 const float zTop = std::sin(angle);
1312 const float u =
static_cast<float>(i) /
static_cast<float>(segments);
1313 vertices.insert(vertices.end(), {
1323 vertices.insert(vertices.end(), {
1335 for (
int i = 0; i < segments; ++i) {
1336 const int current = i * 2;
1337 const int next = (i + 1) * 2;
1338 indices.insert(indices.end(), {
1339 static_cast<uint32_t>(current),
1340 static_cast<uint32_t>(current + 1),
1341 static_cast<uint32_t>(next),
1342 static_cast<uint32_t>(next),
1343 static_cast<uint32_t>(current + 1),
1344 static_cast<uint32_t>(next + 1),
1348 const uint32_t bottomCenterIndex =
static_cast<uint32_t
>(vertices.size() / 8);
1349 vertices.insert(vertices.end(), {
1360 const uint32_t bottomCapStart =
static_cast<uint32_t
>(vertices.size() / 8);
1361 for (
int i = 0; i <= segments; ++i) {
1362 const float angle =
static_cast<float>(i) /
static_cast<float>(segments) * 2.0f * 3.14159265358979323846f;
1363 const float x = std::cos(angle) * bottomCapScale;
1364 const float z = std::sin(angle) * bottomCapScale;
1365 vertices.insert(vertices.end(), {
1369 0.5f + x * 0.5f / bottomCapScale,
1370 0.5f + z * 0.5f / bottomCapScale,
1376 for (
int i = 0; i < segments; ++i) {
1377 indices.insert(indices.end(), {
1379 bottomCapStart + static_cast<uint32_t>(i + 1),
1380 bottomCapStart + static_cast<uint32_t>(i),
1384 const uint32_t topCenterIndex =
static_cast<uint32_t
>(vertices.size() / 8);
1385 vertices.insert(vertices.end(), {
1396 const uint32_t topCapStart =
static_cast<uint32_t
>(vertices.size() / 8);
1397 for (
int i = 0; i <= segments; ++i) {
1398 const float angle =
static_cast<float>(i) /
static_cast<float>(segments) * 2.0f * 3.14159265358979323846f;
1399 const float x = std::cos(angle);
1400 const float z = std::sin(angle);
1401 vertices.insert(vertices.end(), {
1412 for (
int i = 0; i < segments; ++i) {
1413 indices.insert(indices.end(), {
1415 topCapStart + static_cast<uint32_t>(i),
1416 topCapStart + static_cast<uint32_t>(i + 1),
1420 vertexCount =
static_cast<uint32_t
>(vertices.size() / 8);
1421 indexCount =
static_cast<uint32_t
>(indices.size());
1423 std::vector<PillarVertex> pillarVertices(vertexCount);
1424 for (uint32_t i = 0; i < vertexCount; ++i) {
1425 const size_t base =
static_cast<size_t>(i) * 8;
1426 pillarVertices[i].position = glm::vec3(vertices[base + 0], vertices[base + 1], vertices[base + 2]);
1427 pillarVertices[i].texCoord = glm::vec2(vertices[base + 3], vertices[base + 4]);
1428 pillarVertices[i].normal = glm::vec3(vertices[base + 5], vertices[base + 6], vertices[base + 7]);
1431 createBuffer(
static_cast<VkDeviceSize
>(pillarVertices.size() *
sizeof(PillarVertex)),
1432 VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
1433 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
1436 void *mapped =
nullptr;
1437 vkMapMemory(window->getDevice(), vertexMemory, 0, VK_WHOLE_SIZE, 0, &mapped);
1438 std::memcpy(mapped, pillarVertices.data(), pillarVertices.size() *
sizeof(PillarVertex));
1439 vkUnmapMemory(window->getDevice(), vertexMemory);
1441 createBuffer(
static_cast<VkDeviceSize
>(indices.size() *
sizeof(uint32_t)),
1442 VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
1443 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
1446 vkMapMemory(window->getDevice(), indexMemory, 0, VK_WHOLE_SIZE, 0, &mapped);
1447 std::memcpy(mapped, indices.data(), indices.size() *
sizeof(uint32_t));
1448 vkUnmapMemory(window->getDevice(), indexMemory);
1451 void loadTexture([[maybe_unused]]
const std::string &textureManifestPath,
const std::string &textureBasePath) {
1452 SDL_Surface *surface =
mxvk::LoadPNG((textureBasePath +
"/ground.png").c_str());
1453 if (surface ==
nullptr) {
1454 throw mxvk::Exception(
"walk: failed to load raw pillar texture");
1457 const uint32_t width =
static_cast<uint32_t
>(surface->w);
1458 const uint32_t height =
static_cast<uint32_t
>(surface->h);
1459 const VkDeviceSize imageSize =
static_cast<VkDeviceSize
>(width) *
static_cast<VkDeviceSize
>(height) * 4U;
1461 VkBuffer stagingBuffer = VK_NULL_HANDLE;
1462 VkDeviceMemory stagingMemory = VK_NULL_HANDLE;
1463 createBuffer(imageSize,
1464 VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
1465 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
1469 void *mapped =
nullptr;
1470 vkMapMemory(window->getDevice(), stagingMemory, 0, imageSize, 0, &mapped);
1471 std::memcpy(mapped, surface->pixels,
static_cast<size_t>(imageSize));
1472 vkUnmapMemory(window->getDevice(), stagingMemory);
1474 createImage(width, height,
1475 VK_FORMAT_R8G8B8A8_UNORM,
1476 VK_IMAGE_TILING_OPTIMAL,
1477 VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
1478 VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
1482 transitionImageLayout(textureImage, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
1483 copyBufferToImage(stagingBuffer, textureImage, width, height);
1484 transitionImageLayout(textureImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
1486 textureView = createImageView(textureImage, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_ASPECT_COLOR_BIT);
1488 vkDestroyBuffer(window->getDevice(), stagingBuffer,
nullptr);
1489 vkFreeMemory(window->getDevice(), stagingMemory,
nullptr);
1490 SDL_DestroySurface(surface);
1493 mxvk::VK_Window *window =
nullptr;
1494 std::vector<char> vertexSpv{};
1495 std::vector<char> fragmentSpv{};
1497 uint32_t vertexCount = 0;
1498 uint32_t indexCount = 0;
1499 VkBuffer vertexBuffer = VK_NULL_HANDLE;
1500 VkDeviceMemory vertexMemory = VK_NULL_HANDLE;
1501 VkBuffer indexBuffer = VK_NULL_HANDLE;
1502 VkDeviceMemory indexMemory = VK_NULL_HANDLE;
1504 VkImage textureImage = VK_NULL_HANDLE;
1505 VkDeviceMemory textureMemory = VK_NULL_HANDLE;
1506 VkImageView textureView = VK_NULL_HANDLE;
1507 VkSampler textureSampler = VK_NULL_HANDLE;
1509 VkDescriptorSetLayout descriptorSetLayout = VK_NULL_HANDLE;
1510 VkDescriptorPool descriptorPool = VK_NULL_HANDLE;
1511 std::vector<VkDescriptorSet> descriptorSets{};
1513 VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
1514 VkPipeline pipeline = VK_NULL_HANDLE;
1516 std::vector<VkBuffer> uniformBuffers{};
1517 std::vector<VkDeviceMemory> uniformBufferMemory{};
1518 std::vector<void *> uniformBuffersMapped{};
1521 class RawWallRenderer {
1524 glm::vec3 position{0.0f};
1525 glm::vec2 texCoord{0.0f};
1526 glm::vec3 normal{0.0f};
1529 struct WallUniforms {
1530 glm::mat4 view{1.0f};
1531 glm::mat4 proj{1.0f};
1536 const std::string &textureManifestPath,
1537 const std::string &textureBasePath,
1538 const std::vector<char> &vertexShaderSpv,
1539 const std::vector<char> &fragmentShaderSpv) {
1540 if (targetWindow ==
nullptr) {
1541 throw mxvk::Exception(
"walk: raw wall renderer requires a valid window");
1543 window = targetWindow;
1544 vertSpv = vertexShaderSpv;
1545 fragSpv = fragmentShaderSpv;
1547 if (!window->ensureRenderResources()) {
1548 throw mxvk::Exception(
"walk: raw wall renderer requires render resources");
1552 loadTexture(textureManifestPath, textureBasePath);
1553 createTextureSampler();
1554 createDescriptorSetLayout();
1555 createUniformBuffers();
1556 createDescriptorPool();
1557 createDescriptorSets();
1562 if (targetWindow ==
nullptr || targetWindow->
getDevice() == VK_NULL_HANDLE) {
1566 window = targetWindow;
1568 destroyDescriptors();
1569 createDescriptorSetLayout();
1570 createUniformBuffers();
1571 createDescriptorPool();
1572 createDescriptorSets();
1579 if (window ==
nullptr || window->getDevice() == VK_NULL_HANDLE || newFragSpv.empty()) {
1582 vkDeviceWaitIdle(window->getDevice());
1583 fragSpv = newFragSpv;
1589 if (targetWindow ==
nullptr || targetWindow->
getDevice() == VK_NULL_HANDLE) {
1593 window = targetWindow;
1595 destroyDescriptors();
1602 uint32_t imageIndex,
1603 const std::vector<WallSegment> &walls,
1604 float wallThickness,
1605 const glm::mat4 &view,
1606 const glm::mat4 &proj,
1607 const glm::vec4 &fx) {
1608 if (cmd == VK_NULL_HANDLE || pipeline == VK_NULL_HANDLE || pipelineLayout == VK_NULL_HANDLE) {
1611 if (imageIndex >= uniformBuffersMapped.size() || descriptorSets.empty() || vertexBuffer == VK_NULL_HANDLE || indexBuffer == VK_NULL_HANDLE) {
1616 uniforms.view = view;
1617 uniforms.proj = proj;
1619 std::memcpy(uniformBuffersMapped[imageIndex], &uniforms,
sizeof(
WallUniforms));
1621 vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
1622 vkCmdBindDescriptorSets(cmd,
1623 VK_PIPELINE_BIND_POINT_GRAPHICS,
1627 &descriptorSets[imageIndex],
1631 const VkDeviceSize offset = 0;
1632 vkCmdBindVertexBuffers(cmd, 0, 1, &vertexBuffer, &offset);
1633 vkCmdBindIndexBuffer(cmd, indexBuffer, 0, VK_INDEX_TYPE_UINT32);
1635 const float thickness = std::max(0.02f, wallThickness);
1638 const float wallOverlap = thickness * 0.55f;
1640 const glm::vec3 center = (segment.start + segment.end) * 0.5f;
1641 const glm::vec3 span = segment.end - segment.start;
1642 const float length = glm::length(span);
1643 if (length < 0.0001f) {
1648 constexpr float baseSink = 0.01f;
1649 glm::mat4 model = glm::translate(glm::mat4(1.0f), glm::vec3(center.x, -baseSink, center.z));
1650 model = glm::rotate(model, std::atan2(span.z, span.x), glm::vec3(0.0f, 1.0f, 0.0f));
1651 model = glm::scale(model, glm::vec3(length + wallOverlap * 2.0f, segment.height, thickness));
1652 vkCmdPushConstants(cmd, pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0,
sizeof(glm::mat4), &model);
1653 vkCmdDrawIndexed(cmd, indexCount, 1, 0, 0, 0);
1658 [[nodiscard]] uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties)
const {
1659 VkPhysicalDeviceMemoryProperties memProperties{};
1660 vkGetPhysicalDeviceMemoryProperties(window->getPhysicalDevice(), &memProperties);
1661 for (uint32_t i = 0; i < memProperties.memoryTypeCount; ++i) {
1662 if ((typeFilter & (1u << i)) != 0u && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) {
1666 throw mxvk::Exception(
"walk: failed to find suitable memory type for raw wall renderer");
1669 void createBuffer(VkDeviceSize size,
1670 VkBufferUsageFlags usage,
1671 VkMemoryPropertyFlags properties,
1673 VkDeviceMemory &bufferMemory)
const {
1674 VkBufferCreateInfo bufferInfo{};
1675 bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
1676 bufferInfo.size = size;
1677 bufferInfo.usage = usage;
1678 bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
1680 if (vkCreateBuffer(window->getDevice(), &bufferInfo,
nullptr, &buffer) != VK_SUCCESS) {
1681 throw mxvk::Exception(
"walk: failed to create raw wall buffer");
1684 VkMemoryRequirements requirements{};
1685 vkGetBufferMemoryRequirements(window->getDevice(), buffer, &requirements);
1687 VkMemoryAllocateInfo allocInfo{};
1688 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
1689 allocInfo.allocationSize = requirements.size;
1692 allocInfo.memoryTypeIndex = findMemoryType(requirements.memoryTypeBits, properties);
1693 if (vkAllocateMemory(window->getDevice(), &allocInfo,
nullptr, &bufferMemory) != VK_SUCCESS) {
1694 throw mxvk::Exception(
"walk: failed to allocate raw wall buffer memory");
1697 if (vkBindBufferMemory(window->getDevice(), buffer, bufferMemory, 0) != VK_SUCCESS) {
1698 throw mxvk::Exception(
"walk: failed to bind raw wall buffer memory");
1701 if (bufferMemory != VK_NULL_HANDLE) {
1702 vkFreeMemory(window->getDevice(), bufferMemory,
nullptr);
1703 bufferMemory = VK_NULL_HANDLE;
1705 if (buffer != VK_NULL_HANDLE) {
1706 vkDestroyBuffer(window->getDevice(), buffer,
nullptr);
1707 buffer = VK_NULL_HANDLE;
1713 void buildGeometry() {
1716 std::vector<WallVertex> verts;
1717 std::vector<uint32_t> inds;
1721 const auto addFace = [&verts, &inds](
const glm::vec3 &v0,
1722 const glm::vec3 &v1,
1723 const glm::vec3 &v2,
1724 const glm::vec3 &v3,
1725 const glm::vec3 &normal) {
1726 const uint32_t base =
static_cast<uint32_t
>(verts.size());
1727 verts.push_back({v0, glm::vec2(0.0f, 0.0f), normal});
1728 verts.push_back({v1, glm::vec2(1.0f, 0.0f), normal});
1729 verts.push_back({v2, glm::vec2(1.0f, 1.0f), normal});
1730 verts.push_back({v3, glm::vec2(0.0f, 1.0f), normal});
1731 inds.insert(inds.end(), {base + 0, base + 1, base + 2, base + 2, base + 3, base + 0});
1734 constexpr float x = 0.5f;
1735 constexpr float z = 0.5f;
1736 constexpr float y0 = 0.0f;
1737 constexpr float y1 = 1.0f;
1739 addFace(glm::vec3(-x, y0, z), glm::vec3(x, y0, z), glm::vec3(x, y1, z), glm::vec3(-x, y1, z), glm::vec3(0.0f, 0.0f, 1.0f));
1740 addFace(glm::vec3(x, y0, -z), glm::vec3(-x, y0, -z), glm::vec3(-x, y1, -z), glm::vec3(x, y1, -z), glm::vec3(0.0f, 0.0f, -1.0f));
1741 addFace(glm::vec3(x, y0, z), glm::vec3(x, y0, -z), glm::vec3(x, y1, -z), glm::vec3(x, y1, z), glm::vec3(1.0f, 0.0f, 0.0f));
1742 addFace(glm::vec3(-x, y0, -z), glm::vec3(-x, y0, z), glm::vec3(-x, y1, z), glm::vec3(-x, y1, -z), glm::vec3(-1.0f, 0.0f, 0.0f));
1743 addFace(glm::vec3(-x, y1, z), glm::vec3(x, y1, z), glm::vec3(x, y1, -z), glm::vec3(-x, y1, -z), glm::vec3(0.0f, 1.0f, 0.0f));
1744 addFace(glm::vec3(-x, y0, -z), glm::vec3(x, y0, -z), glm::vec3(x, y0, z), glm::vec3(-x, y0, z), glm::vec3(0.0f, -1.0f, 0.0f));
1746 vertexCount =
static_cast<uint32_t
>(verts.size());
1747 indexCount =
static_cast<uint32_t
>(inds.size());
1749 createBuffer(
static_cast<VkDeviceSize
>(verts.size() *
sizeof(WallVertex)),
1750 VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
1751 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
1754 void *mapped =
nullptr;
1755 vkMapMemory(window->getDevice(), vertexMemory, 0, VK_WHOLE_SIZE, 0, &mapped);
1756 std::memcpy(mapped, verts.data(), verts.size() *
sizeof(WallVertex));
1757 vkUnmapMemory(window->getDevice(), vertexMemory);
1759 createBuffer(
static_cast<VkDeviceSize
>(inds.size() *
sizeof(uint32_t)),
1760 VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
1761 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
1764 vkMapMemory(window->getDevice(), indexMemory, 0, VK_WHOLE_SIZE, 0, &mapped);
1765 std::memcpy(mapped, inds.data(), inds.size() *
sizeof(uint32_t));
1766 vkUnmapMemory(window->getDevice(), indexMemory);
1769 void loadTexture([[maybe_unused]]
const std::string &textureManifestPath,
const std::string &textureBasePath) {
1770 SDL_Surface *surface =
mxvk::LoadPNG((textureBasePath +
"/wall_bricks.png").c_str());
1771 if (surface ==
nullptr) {
1772 throw mxvk::Exception(
"walk: failed to load raw wall texture");
1775 const uint32_t width =
static_cast<uint32_t
>(surface->w);
1776 const uint32_t height =
static_cast<uint32_t
>(surface->h);
1777 const VkDeviceSize imageSize =
static_cast<VkDeviceSize
>(width) *
static_cast<VkDeviceSize
>(height) * 4U;
1779 VkBuffer stagingBuffer = VK_NULL_HANDLE;
1780 VkDeviceMemory stagingMemory = VK_NULL_HANDLE;
1781 createBuffer(imageSize,
1782 VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
1783 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
1787 void *mapped =
nullptr;
1788 vkMapMemory(window->getDevice(), stagingMemory, 0, imageSize, 0, &mapped);
1789 std::memcpy(mapped, surface->pixels,
static_cast<size_t>(imageSize));
1790 vkUnmapMemory(window->getDevice(), stagingMemory);
1792 createImage(width, height,
1793 VK_FORMAT_R8G8B8A8_UNORM,
1794 VK_IMAGE_TILING_OPTIMAL,
1795 VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
1796 VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
1800 transitionImageLayout(textureImage, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
1801 copyBufferToImage(stagingBuffer, textureImage, width, height);
1802 transitionImageLayout(textureImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
1804 textureView = createImageView(textureImage, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_ASPECT_COLOR_BIT);
1806 vkDestroyBuffer(window->getDevice(), stagingBuffer,
nullptr);
1807 vkFreeMemory(window->getDevice(), stagingMemory,
nullptr);
1808 SDL_DestroySurface(surface);
1811 void createTextureSampler() {
1812 if (textureSampler != VK_NULL_HANDLE) {
1816 VkPhysicalDeviceFeatures deviceFeatures{};
1817 vkGetPhysicalDeviceFeatures(window->getPhysicalDevice(), &deviceFeatures);
1818 VkPhysicalDeviceProperties deviceProperties{};
1819 vkGetPhysicalDeviceProperties(window->getPhysicalDevice(), &deviceProperties);
1820 const bool anisotropySupported = deviceFeatures.samplerAnisotropy == VK_TRUE;
1821 const float anisotropyLevel = anisotropySupported
1822 ? std::min(8.0f, deviceProperties.limits.maxSamplerAnisotropy)
1825 VkSamplerCreateInfo samplerInfo{};
1826 samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
1827 samplerInfo.magFilter = VK_FILTER_LINEAR;
1828 samplerInfo.minFilter = VK_FILTER_LINEAR;
1829 samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT;
1830 samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT;
1831 samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT;
1832 samplerInfo.anisotropyEnable = anisotropySupported ? VK_TRUE : VK_FALSE;
1833 samplerInfo.maxAnisotropy = anisotropyLevel;
1834 samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK;
1835 samplerInfo.unnormalizedCoordinates = VK_FALSE;
1836 samplerInfo.compareEnable = VK_FALSE;
1837 samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS;
1838 samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
1840 if (vkCreateSampler(window->getDevice(), &samplerInfo,
nullptr, &textureSampler) != VK_SUCCESS) {
1841 throw mxvk::Exception(
"walk: failed to create raw wall texture sampler");
1845 void createDescriptorSetLayout() {
1846 if (descriptorSetLayout != VK_NULL_HANDLE) {
1850 VkDescriptorSetLayoutBinding samplerBinding{};
1851 samplerBinding.binding = 0;
1852 samplerBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
1853 samplerBinding.descriptorCount = 1;
1854 samplerBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
1856 VkDescriptorSetLayoutBinding uboBinding{};
1857 uboBinding.binding = 1;
1858 uboBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
1859 uboBinding.descriptorCount = 1;
1860 uboBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
1862 const std::array<VkDescriptorSetLayoutBinding, 2> bindings = {samplerBinding, uboBinding};
1864 VkDescriptorSetLayoutCreateInfo layoutInfo{};
1865 layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
1866 layoutInfo.bindingCount =
static_cast<uint32_t
>(bindings.size());
1867 layoutInfo.pBindings = bindings.data();
1869 if (vkCreateDescriptorSetLayout(window->getDevice(), &layoutInfo,
nullptr, &descriptorSetLayout) != VK_SUCCESS) {
1870 throw mxvk::Exception(
"walk: failed to create raw wall descriptor set layout");
1874 void createUniformBuffers() {
1875 destroyUniformBuffers();
1877 const size_t frameCount = window->getSwapchainImageCount();
1878 if (frameCount == 0) {
1882 uniformBuffers.resize(frameCount, VK_NULL_HANDLE);
1883 uniformBufferMemory.resize(frameCount, VK_NULL_HANDLE);
1884 uniformBuffersMapped.resize(frameCount,
nullptr);
1886 for (
size_t i = 0; i < frameCount; ++i) {
1887 createBuffer(
sizeof(WallUniforms),
1888 VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
1889 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
1891 uniformBufferMemory[i]);
1892 vkMapMemory(window->getDevice(), uniformBufferMemory[i], 0,
sizeof(WallUniforms), 0, &uniformBuffersMapped[i]);
1896 void createDescriptorPool() {
1897 const uint32_t frameCount =
static_cast<uint32_t
>(window->getSwapchainImageCount());
1898 std::array<VkDescriptorPoolSize, 2> poolSizes{};
1899 poolSizes[0].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
1900 poolSizes[0].descriptorCount = frameCount;
1901 poolSizes[1].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
1902 poolSizes[1].descriptorCount = frameCount;
1904 VkDescriptorPoolCreateInfo poolInfo{};
1905 poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
1906 poolInfo.poolSizeCount =
static_cast<uint32_t
>(poolSizes.size());
1907 poolInfo.pPoolSizes = poolSizes.data();
1908 poolInfo.maxSets = frameCount;
1910 if (vkCreateDescriptorPool(window->getDevice(), &poolInfo,
nullptr, &descriptorPool) != VK_SUCCESS) {
1911 throw mxvk::Exception(
"walk: failed to create raw wall descriptor pool");
1915 void createDescriptorSets() {
1916 const size_t frameCount = window->getSwapchainImageCount();
1917 std::vector<VkDescriptorSetLayout> layouts(frameCount, descriptorSetLayout);
1919 VkDescriptorSetAllocateInfo allocInfo{};
1920 allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
1921 allocInfo.descriptorPool = descriptorPool;
1922 allocInfo.descriptorSetCount =
static_cast<uint32_t
>(frameCount);
1923 allocInfo.pSetLayouts = layouts.data();
1925 descriptorSets.resize(frameCount, VK_NULL_HANDLE);
1926 if (vkAllocateDescriptorSets(window->getDevice(), &allocInfo, descriptorSets.data()) != VK_SUCCESS) {
1927 throw mxvk::Exception(
"walk: failed to allocate raw wall descriptor sets");
1930 VkDescriptorImageInfo imageInfo{};
1931 imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
1932 imageInfo.imageView = textureView;
1933 imageInfo.sampler = textureSampler;
1935 for (
size_t i = 0; i < frameCount; ++i) {
1936 VkDescriptorBufferInfo bufferInfo{};
1937 bufferInfo.buffer = uniformBuffers[i];
1938 bufferInfo.offset = 0;
1939 bufferInfo.range =
sizeof(WallUniforms);
1941 std::array<VkWriteDescriptorSet, 2> writes{};
1942 writes[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
1943 writes[0].dstSet = descriptorSets[i];
1944 writes[0].dstBinding = 0;
1945 writes[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
1946 writes[0].descriptorCount = 1;
1947 writes[0].pImageInfo = &imageInfo;
1949 writes[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
1950 writes[1].dstSet = descriptorSets[i];
1951 writes[1].dstBinding = 1;
1952 writes[1].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
1953 writes[1].descriptorCount = 1;
1954 writes[1].pBufferInfo = &bufferInfo;
1956 vkUpdateDescriptorSets(window->getDevice(),
static_cast<uint32_t
>(writes.size()), writes.data(), 0,
nullptr);
1960 void createPipeline() {
1961 if (descriptorSetLayout == VK_NULL_HANDLE || vertSpv.empty() || fragSpv.empty() || window->getSwapchainFormat() == VK_FORMAT_UNDEFINED) {
1968 VkPipelineShaderStageCreateInfo vertStage{};
1969 vertStage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
1970 vertStage.stage = VK_SHADER_STAGE_VERTEX_BIT;
1971 vertStage.module = vertModule;
1972 vertStage.pName =
"main";
1974 VkPipelineShaderStageCreateInfo fragStage{};
1975 fragStage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
1976 fragStage.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
1977 fragStage.module = fragModule;
1978 fragStage.pName =
"main";
1979 const std::array<VkPipelineShaderStageCreateInfo, 2> stages = {vertStage, fragStage};
1981 VkVertexInputBindingDescription binding{};
1982 binding.binding = 0;
1983 binding.stride =
sizeof(WallVertex);
1984 binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
1986 std::array<VkVertexInputAttributeDescription, 3> attrs{};
1987 attrs[0] = {0, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(WallVertex, position)};
1988 attrs[1] = {1, 0, VK_FORMAT_R32G32_SFLOAT, offsetof(WallVertex, texCoord)};
1989 attrs[2] = {2, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(WallVertex, normal)};
1991 VkPipelineVertexInputStateCreateInfo vertexInput{};
1992 vertexInput.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
1993 vertexInput.vertexBindingDescriptionCount = 1;
1994 vertexInput.pVertexBindingDescriptions = &binding;
1995 vertexInput.vertexAttributeDescriptionCount =
static_cast<uint32_t
>(attrs.size());
1996 vertexInput.pVertexAttributeDescriptions = attrs.data();
1998 VkPipelineInputAssemblyStateCreateInfo inputAssembly{};
1999 inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
2000 inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
2002 const std::array<VkDynamicState, 2> dynamicStates = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
2003 VkPipelineDynamicStateCreateInfo dynamicInfo{};
2004 dynamicInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
2005 dynamicInfo.dynamicStateCount =
static_cast<uint32_t
>(dynamicStates.size());
2006 dynamicInfo.pDynamicStates = dynamicStates.data();
2008 VkPipelineViewportStateCreateInfo viewportState{};
2009 viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
2010 viewportState.viewportCount = 1;
2011 viewportState.scissorCount = 1;
2013 VkPipelineRasterizationStateCreateInfo rasterizer{};
2014 rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
2015 rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
2016 rasterizer.cullMode = VK_CULL_MODE_NONE;
2017 rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE;
2018 rasterizer.lineWidth = 1.0f;
2020 VkPipelineMultisampleStateCreateInfo multisample{};
2021 multisample.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
2022 multisample.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
2024 VkPipelineDepthStencilStateCreateInfo depthStencil{};
2025 depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
2026 depthStencil.depthTestEnable = VK_TRUE;
2027 depthStencil.depthWriteEnable = VK_TRUE;
2028 depthStencil.depthCompareOp = VK_COMPARE_OP_LESS;
2030 VkPipelineColorBlendAttachmentState blendAttachment{};
2031 blendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
2032 blendAttachment.blendEnable = VK_FALSE;
2034 VkPipelineColorBlendStateCreateInfo colorBlend{};
2035 colorBlend.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
2036 colorBlend.attachmentCount = 1;
2037 colorBlend.pAttachments = &blendAttachment;
2039 VkPushConstantRange pushRange{};
2040 pushRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
2041 pushRange.offset = 0;
2042 pushRange.size =
sizeof(glm::mat4);
2044 VkPipelineLayoutCreateInfo layoutInfo{};
2045 layoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
2046 layoutInfo.setLayoutCount = 1;
2047 layoutInfo.pSetLayouts = &descriptorSetLayout;
2048 layoutInfo.pushConstantRangeCount = 1;
2049 layoutInfo.pPushConstantRanges = &pushRange;
2051 if (vkCreatePipelineLayout(window->getDevice(), &layoutInfo,
nullptr, &pipelineLayout) != VK_SUCCESS) {
2052 vkDestroyShaderModule(window->getDevice(), fragModule,
nullptr);
2053 vkDestroyShaderModule(window->getDevice(), vertModule,
nullptr);
2054 throw mxvk::Exception(
"walk: failed to create raw wall pipeline layout");
2057 const VkFormat colorFormat = window->getSwapchainFormat();
2058 const VkFormat depthFormat = window->getDepthFormat();
2059 VkPipelineRenderingCreateInfo renderingInfo{};
2060 renderingInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO;
2061 renderingInfo.colorAttachmentCount = 1;
2062 renderingInfo.pColorAttachmentFormats = &colorFormat;
2063 if (depthFormat != VK_FORMAT_UNDEFINED) {
2064 renderingInfo.depthAttachmentFormat = depthFormat;
2067 VkGraphicsPipelineCreateInfo pipelineInfo{};
2068 pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
2069 pipelineInfo.pNext = &renderingInfo;
2070 pipelineInfo.stageCount =
static_cast<uint32_t
>(stages.size());
2071 pipelineInfo.pStages = stages.data();
2072 pipelineInfo.pVertexInputState = &vertexInput;
2073 pipelineInfo.pInputAssemblyState = &inputAssembly;
2074 pipelineInfo.pViewportState = &viewportState;
2075 pipelineInfo.pRasterizationState = &rasterizer;
2076 pipelineInfo.pMultisampleState = &multisample;
2077 pipelineInfo.pDepthStencilState = &depthStencil;
2078 pipelineInfo.pColorBlendState = &colorBlend;
2079 pipelineInfo.pDynamicState = &dynamicInfo;
2080 pipelineInfo.layout = pipelineLayout;
2081 pipelineInfo.renderPass = VK_NULL_HANDLE;
2083 if (vkCreateGraphicsPipelines(window->getDevice(), VK_NULL_HANDLE, 1, &pipelineInfo,
nullptr, &pipeline) != VK_SUCCESS) {
2084 vkDestroyPipelineLayout(window->getDevice(), pipelineLayout,
nullptr);
2085 pipelineLayout = VK_NULL_HANDLE;
2086 vkDestroyShaderModule(window->getDevice(), fragModule,
nullptr);
2087 vkDestroyShaderModule(window->getDevice(), vertModule,
nullptr);
2088 throw mxvk::Exception(
"walk: failed to create raw wall graphics pipeline");
2091 vkDestroyShaderModule(window->getDevice(), fragModule,
nullptr);
2092 vkDestroyShaderModule(window->getDevice(), vertModule,
nullptr);
2095 void destroyPipeline() {
2096 if (window ==
nullptr || window->getDevice() == VK_NULL_HANDLE) {
2097 pipeline = VK_NULL_HANDLE;
2098 pipelineLayout = VK_NULL_HANDLE;
2102 if (pipeline != VK_NULL_HANDLE) {
2103 vkDestroyPipeline(window->getDevice(), pipeline,
nullptr);
2104 pipeline = VK_NULL_HANDLE;
2106 if (pipelineLayout != VK_NULL_HANDLE) {
2107 vkDestroyPipelineLayout(window->getDevice(), pipelineLayout,
nullptr);
2108 pipelineLayout = VK_NULL_HANDLE;
2112 void destroyDescriptors() {
2113 if (window ==
nullptr || window->getDevice() == VK_NULL_HANDLE) {
2114 descriptorSets.clear();
2115 descriptorPool = VK_NULL_HANDLE;
2116 descriptorSetLayout = VK_NULL_HANDLE;
2117 destroyUniformBuffers();
2121 descriptorSets.clear();
2122 if (descriptorPool != VK_NULL_HANDLE) {
2123 vkDestroyDescriptorPool(window->getDevice(), descriptorPool,
nullptr);
2124 descriptorPool = VK_NULL_HANDLE;
2126 if (descriptorSetLayout != VK_NULL_HANDLE) {
2127 vkDestroyDescriptorSetLayout(window->getDevice(), descriptorSetLayout,
nullptr);
2128 descriptorSetLayout = VK_NULL_HANDLE;
2130 destroyUniformBuffers();
2133 void destroyUniformBuffers() {
2134 if (window ==
nullptr || window->getDevice() == VK_NULL_HANDLE) {
2135 uniformBuffers.clear();
2136 uniformBufferMemory.clear();
2137 uniformBuffersMapped.clear();
2141 for (
size_t i = 0; i < uniformBuffers.size(); ++i) {
2142 if (uniformBuffersMapped[i] !=
nullptr) {
2143 vkUnmapMemory(window->getDevice(), uniformBufferMemory[i]);
2144 uniformBuffersMapped[i] =
nullptr;
2146 if (uniformBuffers[i] != VK_NULL_HANDLE) {
2147 vkDestroyBuffer(window->getDevice(), uniformBuffers[i],
nullptr);
2149 if (uniformBufferMemory[i] != VK_NULL_HANDLE) {
2150 vkFreeMemory(window->getDevice(), uniformBufferMemory[i],
nullptr);
2154 uniformBuffers.clear();
2155 uniformBufferMemory.clear();
2156 uniformBuffersMapped.clear();
2159 void destroyTexture() {
2160 if (window ==
nullptr || window->getDevice() == VK_NULL_HANDLE) {
2161 textureView = VK_NULL_HANDLE;
2162 textureImage = VK_NULL_HANDLE;
2163 textureMemory = VK_NULL_HANDLE;
2164 textureSampler = VK_NULL_HANDLE;
2168 if (textureView != VK_NULL_HANDLE) {
2169 vkDestroyImageView(window->getDevice(), textureView,
nullptr);
2170 textureView = VK_NULL_HANDLE;
2172 if (textureImage != VK_NULL_HANDLE) {
2173 vkDestroyImage(window->getDevice(), textureImage,
nullptr);
2174 textureImage = VK_NULL_HANDLE;
2176 if (textureMemory != VK_NULL_HANDLE) {
2177 vkFreeMemory(window->getDevice(), textureMemory,
nullptr);
2178 textureMemory = VK_NULL_HANDLE;
2180 if (textureSampler != VK_NULL_HANDLE) {
2181 vkDestroySampler(window->getDevice(), textureSampler,
nullptr);
2182 textureSampler = VK_NULL_HANDLE;
2186 void destroyBuffers() {
2187 if (window ==
nullptr || window->getDevice() == VK_NULL_HANDLE) {
2188 vertexBuffer = VK_NULL_HANDLE;
2189 vertexMemory = VK_NULL_HANDLE;
2190 indexBuffer = VK_NULL_HANDLE;
2191 indexMemory = VK_NULL_HANDLE;
2195 if (vertexBuffer != VK_NULL_HANDLE) {
2196 vkDestroyBuffer(window->getDevice(), vertexBuffer,
nullptr);
2197 vertexBuffer = VK_NULL_HANDLE;
2199 if (vertexMemory != VK_NULL_HANDLE) {
2200 vkFreeMemory(window->getDevice(), vertexMemory,
nullptr);
2201 vertexMemory = VK_NULL_HANDLE;
2203 if (indexBuffer != VK_NULL_HANDLE) {
2204 vkDestroyBuffer(window->getDevice(), indexBuffer,
nullptr);
2205 indexBuffer = VK_NULL_HANDLE;
2207 if (indexMemory != VK_NULL_HANDLE) {
2208 vkFreeMemory(window->getDevice(), indexMemory,
nullptr);
2209 indexMemory = VK_NULL_HANDLE;
2213 void createImage(uint32_t width,
2216 VkImageTiling tiling,
2217 VkImageUsageFlags usage,
2218 VkMemoryPropertyFlags properties,
2220 VkDeviceMemory &memory)
const {
2221 VkImageCreateInfo imageInfo{};
2222 imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
2223 imageInfo.imageType = VK_IMAGE_TYPE_2D;
2224 imageInfo.extent.width = width;
2225 imageInfo.extent.height = height;
2226 imageInfo.extent.depth = 1;
2227 imageInfo.mipLevels = 1;
2228 imageInfo.arrayLayers = 1;
2229 imageInfo.format = format;
2230 imageInfo.tiling = tiling;
2231 imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
2232 imageInfo.usage = usage;
2233 imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
2234 imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
2236 if (vkCreateImage(window->getDevice(), &imageInfo,
nullptr, &image) != VK_SUCCESS) {
2237 throw mxvk::Exception(
"walk: failed to create raw wall image");
2240 VkMemoryRequirements requirements{};
2241 vkGetImageMemoryRequirements(window->getDevice(), image, &requirements);
2243 VkMemoryAllocateInfo allocInfo{};
2244 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
2245 allocInfo.allocationSize = requirements.size;
2248 allocInfo.memoryTypeIndex = findMemoryType(requirements.memoryTypeBits, properties);
2249 if (vkAllocateMemory(window->getDevice(), &allocInfo,
nullptr, &memory) != VK_SUCCESS) {
2250 throw mxvk::Exception(
"walk: failed to allocate raw wall image memory");
2253 if (vkBindImageMemory(window->getDevice(), image, memory, 0) != VK_SUCCESS) {
2254 throw mxvk::Exception(
"walk: failed to bind raw wall image memory");
2257 if (memory != VK_NULL_HANDLE) {
2258 vkFreeMemory(window->getDevice(), memory,
nullptr);
2259 memory = VK_NULL_HANDLE;
2261 if (image != VK_NULL_HANDLE) {
2262 vkDestroyImage(window->getDevice(), image,
nullptr);
2263 image = VK_NULL_HANDLE;
2269 VkImageView createImageView(VkImage image, VkFormat format, VkImageAspectFlags aspectFlags)
const {
2270 VkImageViewCreateInfo viewInfo{};
2271 viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
2272 viewInfo.image = image;
2273 viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
2274 viewInfo.format = format;
2275 viewInfo.subresourceRange.aspectMask = aspectFlags;
2276 viewInfo.subresourceRange.baseMipLevel = 0;
2277 viewInfo.subresourceRange.levelCount = 1;
2278 viewInfo.subresourceRange.baseArrayLayer = 0;
2279 viewInfo.subresourceRange.layerCount = 1;
2281 VkImageView imageView = VK_NULL_HANDLE;
2282 if (vkCreateImageView(window->getDevice(), &viewInfo,
nullptr, &imageView) != VK_SUCCESS) {
2283 throw mxvk::Exception(
"walk: failed to create raw wall image view");
2288 [[nodiscard]] VkCommandBuffer beginSingleTimeCommands()
const {
2289 VkCommandBufferAllocateInfo allocInfo{};
2290 allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
2291 allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
2292 allocInfo.commandPool = window->getCommandPool();
2293 allocInfo.commandBufferCount = 1;
2295 VkCommandBuffer commandBuffer = VK_NULL_HANDLE;
2296 if (vkAllocateCommandBuffers(window->getDevice(), &allocInfo, &commandBuffer) != VK_SUCCESS) {
2297 throw mxvk::Exception(
"walk: failed to allocate raw wall command buffer");
2300 VkCommandBufferBeginInfo beginInfo{};
2301 beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
2302 beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
2303 if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) {
2304 vkFreeCommandBuffers(window->getDevice(), window->getCommandPool(), 1, &commandBuffer);
2305 throw mxvk::Exception(
"walk: failed to begin raw wall command buffer");
2308 return commandBuffer;
2311 void endSingleTimeCommands(VkCommandBuffer commandBuffer)
const {
2312 if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) {
2313 vkFreeCommandBuffers(window->getDevice(), window->getCommandPool(), 1, &commandBuffer);
2314 throw mxvk::Exception(
"walk: failed to end raw wall command buffer");
2317 VkSubmitInfo submitInfo{};
2318 submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
2319 submitInfo.commandBufferCount = 1;
2320 submitInfo.pCommandBuffers = &commandBuffer;
2322 if (vkQueueSubmit(window->getGraphicsQueue(), 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS) {
2323 vkFreeCommandBuffers(window->getDevice(), window->getCommandPool(), 1, &commandBuffer);
2324 throw mxvk::Exception(
"walk: failed to submit raw wall command buffer");
2326 if (vkQueueWaitIdle(window->getGraphicsQueue()) != VK_SUCCESS) {
2327 vkFreeCommandBuffers(window->getDevice(), window->getCommandPool(), 1, &commandBuffer);
2328 throw mxvk::Exception(
"walk: failed to wait for raw wall upload queue");
2331 vkFreeCommandBuffers(window->getDevice(), window->getCommandPool(), 1, &commandBuffer);
2334 void transitionImageLayout(VkImage image, VkImageLayout oldLayout, VkImageLayout newLayout)
const {
2335 VkCommandBuffer cmd = beginSingleTimeCommands();
2337 VkImageMemoryBarrier barrier{};
2338 barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
2339 barrier.oldLayout = oldLayout;
2340 barrier.newLayout = newLayout;
2341 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
2342 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
2343 barrier.image = image;
2344 barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
2345 barrier.subresourceRange.baseMipLevel = 0;
2346 barrier.subresourceRange.levelCount = 1;
2347 barrier.subresourceRange.baseArrayLayer = 0;
2348 barrier.subresourceRange.layerCount = 1;
2350 VkPipelineStageFlags sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
2351 VkPipelineStageFlags destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
2352 if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
2353 barrier.srcAccessMask = 0;
2354 barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
2355 }
else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) {
2356 barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
2357 barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
2358 sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
2359 destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
2362 vkCmdPipelineBarrier(cmd, sourceStage, destinationStage, 0, 0,
nullptr, 0,
nullptr, 1, &barrier);
2363 endSingleTimeCommands(cmd);
2366 void copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height)
const {
2367 VkCommandBuffer cmd = beginSingleTimeCommands();
2368 VkBufferImageCopy region{};
2369 region.bufferOffset = 0;
2370 region.bufferRowLength = 0;
2371 region.bufferImageHeight = 0;
2372 region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
2373 region.imageSubresource.mipLevel = 0;
2374 region.imageSubresource.baseArrayLayer = 0;
2375 region.imageSubresource.layerCount = 1;
2376 region.imageOffset = {0, 0, 0};
2377 region.imageExtent = {width, height, 1};
2379 vkCmdCopyBufferToImage(cmd, buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion);
2380 endSingleTimeCommands(cmd);
2383 std::vector<char> vertSpv{};
2384 std::vector<char> fragSpv{};
2386 uint32_t vertexCount = 0;
2387 uint32_t indexCount = 0;
2388 VkBuffer vertexBuffer = VK_NULL_HANDLE;
2389 VkDeviceMemory vertexMemory = VK_NULL_HANDLE;
2390 VkBuffer indexBuffer = VK_NULL_HANDLE;
2391 VkDeviceMemory indexMemory = VK_NULL_HANDLE;
2393 VkImage textureImage = VK_NULL_HANDLE;
2394 VkDeviceMemory textureMemory = VK_NULL_HANDLE;
2395 VkImageView textureView = VK_NULL_HANDLE;
2396 VkSampler textureSampler = VK_NULL_HANDLE;
2398 VkDescriptorSetLayout descriptorSetLayout = VK_NULL_HANDLE;
2399 VkDescriptorPool descriptorPool = VK_NULL_HANDLE;
2400 std::vector<VkDescriptorSet> descriptorSets{};
2402 VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
2403 VkPipeline pipeline = VK_NULL_HANDLE;
2405 std::vector<VkBuffer> uniformBuffers{};
2406 std::vector<VkDeviceMemory> uniformBufferMemory{};
2407 std::vector<void *> uniformBuffersMapped{};
2409 VkDevice device [[maybe_unused]] = VK_NULL_HANDLE;
2410 mxvk::VK_Window *window =
nullptr;
2416 :
mxvk::
VK_IOWindow(args.path,
"FPS Maze Room - MXVK", args.width, args.height, args.fullscreen, args.enable_vsync),
2417 assetRoot((args.path.empty() || args.path ==
".") ? std::string(WALK_ASSET_DIR) : args.path),
2418 shaderRoot(assetRoot +
"/data"),
2419 modelRoot(assetRoot +
"/data") {
2420 logEnv(std::format(
"initializing window {}x{} (fullscreen={})", args.
width, args.
height, args.
fullscreen ?
"true" :
"false"));
2421 logEnv(std::format(
"asset root: {}", assetRoot));
2422 logEnv(std::format(
"model root: {}", modelRoot));
2424 std::mt19937 rng(
static_cast<uint32_t
>(std::chrono::high_resolution_clock::now().time_since_epoch().count()));
2425 world.generate(rng());
2426 logEnv(std::format(
"world generated (walls={}, pillars={}, collectibles={})",
2427 world.walls().size(),
2428 world.pillars().size(),
2429 world.collectibles().size()));
2430 setClearColor(100.0f / 255.0f, 181.0f / 255.0f, 246.0f / 255.0f, 1.0f);
2432 cameraPos = world.startPosition();
2433 yaw = chooseBestSpawnYaw(cameraPos);
2435 updateCameraVectors();
2437 setFont(assetRoot +
"/data/font.ttf", 22);
2439 const std::string vertPath = shaderRoot +
"/model.vert.spv";
2440 const std::string wallFragPath = shaderRoot +
"/wall.frag.spv";
2441 const std::string floorFragPath = shaderRoot +
"/floor.frag.spv";
2442 const std::string pillarVertPath = shaderRoot +
"/pillar.vert.spv";
2443 const std::string pillarFragPath = shaderRoot +
"/pillar.frag.spv";
2444 const std::string objectFragPath = shaderRoot +
"/object.frag.spv";
2445 const std::string bulletFragPath = shaderRoot +
"/bullet.frag.spv";
2446 const std::string particleFragPath = shaderRoot +
"/particle.frag.spv";
2447 const std::string groundTexManifest = assetRoot +
"/data/ground.tex";
2449 modelVertSpv = vertPath;
2450 pillarVertSpv = pillarVertPath;
2451 wallFragSpv = wallFragPath;
2452 floorFragSpv = floorFragPath;
2453 pillarFragSpv = pillarFragPath;
2454 objectFragSpv = objectFragPath;
2455 bulletFragSpv = bulletFragPath;
2457 loadModel(floorModel, modelRoot +
"/cube.mxmod.z", groundTexManifest, assetRoot +
"/data", vertPath, floorFragPath);
2458 loadModel(bulletModel, modelRoot +
"/sphere.mxmod.z",
"",
"", vertPath, bulletFragPath);
2460 logEnv(
"loading wall renderer assets");
2461 rawWallRenderer.load(
this,
2463 assetRoot +
"/data",
2466 logEnv(
"wall renderer ready");
2468 logEnv(
"loading pillar renderer assets");
2469 rawPillarRenderer.load(
this,
2471 assetRoot +
"/data",
2474 logEnv(
"pillar renderer ready");
2476 loadModel(saturnModel, assetRoot +
"/data/saturn.mxmod.z",
2477 assetRoot +
"/data/planet.tex", assetRoot +
"/data", vertPath, objectFragPath);
2478 loadModel(birdModel, assetRoot +
"/data/tux.obj",
2479 assetRoot +
"/data/tux.mtl", assetRoot +
"/data", vertPath, objectFragPath);
2480 loadModel(blasterModel, assetRoot +
"/data/blaster.obj",
2481 assetRoot +
"/data/blaster.mtl", assetRoot +
"/data", vertPath, objectFragPath);
2482 normalizeCollectiblesToModel();
2484 pointParticleVertSpv = shaderRoot +
"/particle_points.vert.spv";
2485 pointParticleFragSpv = shaderRoot +
"/particle_points.frag.spv";
2486 initializePointParticles();
2487 logEnv(
"point-particle pipeline initialized");
2489 loadPostProcessingShaderIndex(args.
shaderPath);
2491 applyPostProcessingShaderSelection();
2493 tryOpenFirstGamepad();
2495 logEnv(
"mouse capture enabled");
2499 logEnv(
"shutting down walk window");
2500 if (gamepad !=
nullptr) {
2501 SDL_CloseGamepad(gamepad);
2505 if (
device != VK_NULL_HANDLE) {
2506 vkDeviceWaitIdle(
device);
2507 destroyPointParticles();
2513 const bool is_left_double_click =
2514 (e.type == SDL_EVENT_MOUSE_BUTTON_DOWN &&
2515 e.button.button == SDL_BUTTON_LEFT &&
2516 e.button.clicks >= 2);
2518 if (is_left_double_click) {
2519 if (SDL_Window *
const sdlWindow =
getSDLWindow(); sdlWindow !=
nullptr) {
2520 SDL_RaiseWindow(sdlWindow);
2521 SDL_SetWindowMouseGrab(sdlWindow,
true);
2522 SDL_SetWindowRelativeMouseMode(sdlWindow,
true);
2525 mouseCapture =
true;
2528 suppressProjectileOnNextLeftDown =
true;
2530 logEnv(
"mouse capture enabled (double-click)");
2537 if (e.type == SDL_EVENT_QUIT) {
2538 logEnv(
"received quit event");
2543 if (e.type == SDL_EVENT_KEY_DOWN) {
2544 if (e.key.key == SDLK_ESCAPE) {
2546 mouseCapture =
false;
2548 suppressProjectileOnNextLeftDown =
false;
2549 logEnv(
"mouse capture disabled (ESC)");
2551 logEnv(
"exit requested by ESC");
2555 }
else if (e.key.key == SDLK_F) {
2557 logEnv(std::format(
"FPS overlay {}", showFps ?
"enabled" :
"disabled"));
2558 }
else if (e.key.key == SDLK_R && !e.key.repeat) {
2559 selectPostProcessingShader(-1);
2560 }
else if (e.key.key == SDLK_T && !e.key.repeat) {
2561 selectPostProcessingShader(1);
2565 if (e.type == SDL_EVENT_GAMEPAD_ADDED) {
2566 logEnv(std::format(
"gamepad added (id={})",
static_cast<int>(e.gdevice.which)));
2567 openGamepad(e.gdevice.which);
2570 if (e.type == SDL_EVENT_GAMEPAD_REMOVED) {
2571 if (gamepad !=
nullptr && e.gdevice.which == gamepadId) {
2572 logEnv(std::format(
"gamepad removed (id={})",
static_cast<int>(e.gdevice.which)));
2573 SDL_CloseGamepad(gamepad);
2579 if (e.type == SDL_EVENT_GAMEPAD_BUTTON_DOWN) {
2580 if (e.gbutton.button == SDL_GAMEPAD_BUTTON_BACK || e.gbutton.button == SDL_GAMEPAD_BUTTON_START) {
2581 logEnv(
"exit requested by gamepad back/start");
2583 }
else if (e.gbutton.button == SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER) {
2585 }
else if (e.gbutton.button == SDL_GAMEPAD_BUTTON_SOUTH && cameraPos.y <= 1.71f) {
2586 jumpVelocity = 0.3f;
2587 logEnv(
"jump triggered by gamepad");
2591 if (e.type == SDL_EVENT_MOUSE_MOTION && mouseCapture) {
2596 yaw +=
static_cast<float>(e.motion.xrel) * mouseSensitivity;
2597 pitch -=
static_cast<float>(e.motion.yrel) * mouseSensitivity;
2598 pitch = glm::clamp(pitch, -89.0f, 89.0f);
2599 updateCameraVectors();
2602 if (e.type == SDL_EVENT_MOUSE_BUTTON_DOWN && e.button.button == SDL_BUTTON_LEFT && mouseCapture) {
2603 if (suppressProjectileOnNextLeftDown) {
2604 suppressProjectileOnNextLeftDown =
false;
2612 tryOpenFirstGamepad();
2613 const auto now = std::chrono::steady_clock::now();
2614 float deltaTime = std::chrono::duration<float>(now - lastTick).count();
2616 deltaTime = std::clamp(deltaTime, 0.0f, 0.05f);
2617 updatePostProcessingShaderUniforms(deltaTime);
2620 updatePlayer(deltaTime);
2622 updateProjectiles(deltaTime);
2623 updateExplosions(deltaTime);
2624 updateCollectibles(deltaTime);
2626 const int aliveObjects = world.activeCollectibles();
2628 printText(std::format(
"Objects left: {}", aliveObjects), 20, 20, {255, 255, 255, 255});
2629 printText(std::format(
"Active Bullets: {}", bullets.size()), 20, 48, {255, 220, 120, 255});
2630 if (showFps && deltaTime > 0.0001f) {
2631 const int fps =
static_cast<int>(1.0f / deltaTime);
2632 printText(std::format(
"FPS: {}", fps), 20, 76, {120, 255, 120, 255});
2636 const int cx =
static_cast<int>(extent.width / 2U);
2637 const int cy =
static_cast<int>(extent.height / 2U);
2638 printText(
"+", cx - 6, cy - 12, {255, 64, 64, 255});
2639 printText(
"3D Room - WASD/Left Stick move, Mouse/Right Stick look, Click/RB shoot, Back/Start quit", 20,
static_cast<int>(extent.height) - 36, {210, 210, 210, 255});
2640 if (!postProcessingShaders.empty()) {
2641 printText(std::format(
"Post FX: {} / {} R/T", postProcessingShaderIndex + 1, postProcessingShaders.size()), 20, 104, {180, 220, 255, 255});
2647 logEnv(
"swapchain recreated; resizing render resources");
2648 floorModel.resize(
this);
2649 rawWallRenderer.resize(
this);
2650 rawPillarRenderer.resize(
this);
2651 saturnModel.resize(
this);
2652 birdModel.resize(
this);
2653 blasterModel.resize(
this);
2654 bulletModel.resize(
this);
2655 rebuildPointParticlePipeline();
2660 const float aspect = (extent.height > 0U)
2661 ?
static_cast<float>(extent.width) /
static_cast<float>(extent.height)
2664 const glm::mat4 view = glm::lookAt(cameraPos, cameraPos + cameraFront, glm::vec3(0.0f, 1.0f, 0.0f));
2665 glm::mat4 proj = glm::perspective(glm::radians(45.0f), aspect, 0.1f, 1000.0f);
2666 proj[1][1] *= -1.0f;
2668 const float t =
static_cast<float>(SDL_GetTicks()) * 0.001f;
2672 constexpr float floorHalfSize = 100.0f;
2673 constexpr float floorThickness = 0.04f;
2674 const glm::vec3 extent = floorModel.modelAxisExtent();
2675 const glm::vec3 srcScale(
2676 (floorHalfSize * 2.0f) / std::max(extent.x, 1e-4f),
2677 floorThickness / std::max(extent.y, 1e-4f),
2678 (floorHalfSize * 2.0f) / std::max(extent.z, 1e-4f));
2679 glm::mat4 floorWorld = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, -0.02f, 0.0f));
2680 floorWorld = glm::scale(floorWorld, srcScale);
2681 renderModel(cmd, imageIndex, floorModel, floorWorld, view, proj,
2682 glm::vec4(0.0f, 0.0f, 0.0f, t),
false);
2685 rawWallRenderer.render(cmd,
2688 world.wallThickness(),
2691 glm::vec4(0.58f, 0.58f, 0.65f, t));
2693 rawPillarRenderer.render(cmd, imageIndex, world.pillars(), view, proj, glm::vec4(0.0f, 0.0f, 0.0f, t));
2695 for (
const Collectible &obj : world.collectibles()) {
2699 glm::mat4 world = glm::translate(glm::mat4(1.0f), obj.position);
2700 world = glm::rotate(world, glm::radians(obj.rotation.y), glm::vec3(0.0f, 1.0f, 0.0f));
2701 world = glm::scale(world, obj.scale);
2703 renderRawModel(cmd, imageIndex, saturnModel, world, view, proj, glm::vec4(cameraPos, 0.0f));
2705 renderRawModel(cmd, imageIndex, birdModel, world, view, proj, glm::vec4(cameraPos, 0.0f));
2713 blasterWorldTransform(),
2716 glm::vec4(cameraPos, 0.0f));
2720 if (!bullet.active) {
2723 if (bullet.lifetime < 0.05f) {
2726 glm::mat4 world = glm::translate(glm::mat4(1.0f), bullet.position);
2727 world = glm::scale(world, glm::vec3(0.07f, 0.07f, 0.20f));
2728 const float fadeProgress = glm::clamp(bullet.lifetime / bullet.maxLifetime, 0.0f, 1.0f);
2729 const float distanceProgress = glm::clamp(bullet.distanceTraveled / bullet.maxDistance, 0.0f, 1.0f);
2730 const float alpha = std::min(1.0f - fadeProgress, 1.0f - distanceProgress);
2731 renderRawModel(cmd, imageIndex, bulletModel, world, view, proj, glm::vec4(alpha, 0.0f, 0.0f, 0.0f));
2733 renderPointParticles(cmd, view, proj);
2737 enum class ProjectileHitType {
2744 struct ProjectileTraceHit {
2745 ProjectileHitType type = ProjectileHitType::None;
2746 glm::vec3 impact{0.0f};
2747 size_t collectibleIndex = 0;
2750 [[nodiscard]] ProjectileTraceHit traceProjectileSegment(
const glm::vec3 &from,
const glm::vec3 &to)
const {
2751 const glm::vec3 dir = to - from;
2752 const float travel = glm::length(dir);
2753 if (travel <= 1e-8f) {
2757 constexpr float sampleStride = 0.03f;
2758 constexpr float projectileRadius = 0.015f;
2759 const int steps = std::max(1,
static_cast<int>(std::ceil(travel / sampleStride)));
2760 for (
int i = 0; i <= steps; ++i) {
2761 const float t =
static_cast<float>(i) /
static_cast<float>(steps);
2762 const glm::vec3 point = from + (dir * t);
2763 if (pointHitsWall3D(point, projectileRadius)) {
2764 return {ProjectileHitType::Wall, point, 0};
2766 if (pointHitsPillar3D(point, projectileRadius)) {
2767 return {ProjectileHitType::Pillar, point, 0};
2769 if (point.y <= 0.0f) {
2770 return {ProjectileHitType::Floor, point, 0};
2782 const std::string &cmd = args[0];
2784 if (cmd ==
"spawn_random" || (cmd ==
"spawn" && args.size() >= 2 && args[1] ==
"random")) {
2785 const int attempts = (args.size() >= 3 && cmd ==
"spawn") ? parseIntOrDefault(args[2], 128)
2786 : ((args.size() >= 2 && cmd ==
"spawn_random") ? parseIntOrDefault(args[1], 128) : 128);
2787 glm::vec3 candidate = cameraPos;
2788 if (!sampleNavigablePoint(1.7f, 0.68f, candidate, std::max(1, attempts))) {
2789 candidate = world.startPosition();
2792 cameraPos = candidate;
2793 yaw = chooseBestSpawnYaw(cameraPos);
2795 updateCameraVectors();
2797 out << std::format(
"Spawned at random location ({:.2f}, {:.2f}, {:.2f})", cameraPos.x, cameraPos.y, cameraPos.z);
2798 logEnv(
"command: spawn_random");
2802 if (cmd ==
"reset" || cmd ==
"reset_collectibles") {
2803 std::vector<Collectible> &collectibles = world.collectibles();
2804 for (
size_t i = 0; i < collectibles.size(); ++i) {
2805 Collectible &obj = collectibles[i];
2807 obj.rotation = glm::vec3(0.0f);
2808 relocateCollectible(i, 2.0f, 128);
2810 resolveCollectibleClusters(2.0f, 4);
2812 out << std::format(
"Collectibles reset. Active collectibles: {}", world.activeCollectibles());
2813 logEnv(
"command: reset collectibles");
2817 if (cmd ==
"add_collectibles" || cmd ==
"add_collectables") {
2818 const int requested = (args.size() >= 2) ? parseIntOrDefault(args[1], 10) : 10;
2819 const int toAdd = std::clamp(requested, 1, 200);
2820 std::uniform_int_distribution<int> typeDist(0, 1);
2821 std::uniform_real_distribution<float> saturnScale(0.4f, 0.8f);
2822 std::uniform_real_distribution<float> saturnRotSpeed(5.0f, 15.0f);
2823 std::uniform_real_distribution<float> birdScale(0.3f, 0.5f);
2824 std::uniform_real_distribution<float> birdRotSpeed(20.0f, 60.0f);
2827 for (
int i = 0; i < toAdd; ++i) {
2829 obj.type = (typeDist(rng) == 0) ? Collectible::Type::Saturn : Collectible::Type::Bird;
2830 if (obj.type == Collectible::Type::Saturn) {
2831 const float scale = saturnScale(rng);
2832 obj.scale = glm::vec3(scale);
2833 obj.rotationSpeed = saturnRotSpeed(rng);
2834 obj.radius = saturnHitRadiusForScale(scale);
2835 obj.hitCenterOffset = saturnHitCenterOffsetForScale(scale);
2837 const float scale = birdScale(rng);
2838 obj.scale = glm::vec3(scale);
2839 obj.rotationSpeed = birdRotSpeed(rng);
2840 obj.radius = birdHitHalfSideForScale(scale);
2841 obj.hitCenterOffset = birdHitCenterOffsetForScale(scale);
2844 bool placed =
false;
2845 for (
int attempt = 0; attempt < 96; ++attempt) {
2846 const float y = (obj.type == Collectible::Type::Bird) ? birdGroundYForScale(obj.scale.x) : 2.5f;
2847 const float placementRadius = placementRadiusForCollectible(obj);
2848 glm::vec3 candidate{};
2849 if (!sampleNavigablePoint(y, placementRadius, candidate, 1)) {
2853 bool overlaps =
false;
2854 for (
const Collectible &existing : world.collectibles()) {
2855 if (!existing.active) {
2858 const float separation = std::max(5.0f, existing.radius + obj.radius + 0.2f);
2859 if (glm::length(existing.position - candidate) < separation) {
2866 obj.position = candidate;
2873 world.collectibles().push_back(obj);
2878 out << std::format(
"Added {} collectible(s). Active collectibles: {}",
2880 world.activeCollectibles());
2881 resolveCollectibleClusters(2.0f, 4);
2882 logEnv(std::format(
"command: add_collectibles requested={} added={}", toAdd, added));
2886 if (cmd ==
"status") {
2887 out << std::format(
"pos=({:.2f}, {:.2f}, {:.2f}) yaw={:.2f} pitch={:.2f}\n"
2888 "walls={} pillars={} collectibles(active/total)={}/{} bullets={} particles={} destroyed={}",
2894 world.walls().size(),
2895 world.pillars().size(),
2896 world.activeCollectibles(),
2897 world.collectibles().size(),
2899 explosionParticles.size(),
2904 if (cmd ==
"teleport") {
2905 if (args.size() < 4) {
2906 out <<
"Usage: teleport <x> <y> <z>";
2913 if (!tryParseFloat(args[1], x) || !tryParseFloat(args[2], y) || !tryParseFloat(args[3], z)) {
2914 out <<
"teleport: invalid numeric argument(s)";
2918 const glm::vec3 candidate{x, y, z};
2919 if (world.checkWallCollision(candidate, 0.68f) || world.checkPillarCollision(candidate, 0.68f)) {
2920 out <<
"teleport blocked: target intersects wall/pillar";
2924 cameraPos = candidate;
2925 out << std::format(
"Teleported to ({:.2f}, {:.2f}, {:.2f})", x, y, z);
2926 logEnv(
"command: teleport");
2930 if (cmd ==
"clear_bullets") {
2931 const std::size_t removed = bullets.size();
2933 out << std::format(
"Cleared {} bullet(s)", removed);
2937 if (cmd ==
"clear_fx") {
2938 const std::size_t removed = explosionParticles.size();
2939 explosionParticles.clear();
2940 out << std::format(
"Cleared {} particle effect(s)", removed);
2944 if (cmd ==
"set_fps") {
2945 if (args.size() < 2) {
2946 out << std::format(
"FPS overlay is currently {}. Usage: set_fps <on|off>", showFps ?
"on" :
"off");
2949 const std::string value = toLowerCopy(args[1]);
2950 if (value ==
"on" || value ==
"1" || value ==
"true") {
2952 out <<
"FPS overlay enabled";
2955 if (value ==
"off" || value ==
"0" || value ==
"false") {
2957 out <<
"FPS overlay disabled";
2961 out <<
"Usage: set_fps <on|off>";
2965 if (cmd ==
"regen_world") {
2966 const uint32_t seed = (args.size() >= 2) ?
static_cast<uint32_t
>(parseIntOrDefault(args[1],
static_cast<int>(
rng())))
2968 world.generate(seed);
2969 normalizeCollectiblesToModel();
2970 cameraPos = world.startPosition();
2971 yaw = chooseBestSpawnYaw(cameraPos);
2973 updateCameraVectors();
2975 explosionParticles.clear();
2978 out << std::format(
"Regenerated world with seed {} (walls={}, pillars={}, collectibles={})",
2980 world.walls().size(),
2981 world.pillars().size(),
2982 world.collectibles().size());
2983 logEnv(std::format(
"command: regen_world seed={}", seed));
2987 if (cmd ==
"set_wall" || cmd ==
"set_floor" || cmd ==
"set_pillar" || cmd ==
"set_object" || cmd ==
"set_bullet") {
2988 if (args.size() < 2) {
2989 out << std::format(
"Usage: {} <shader.spv|full/path/to/shader.spv>", cmd);
2993 const std::string shaderPath = resolveShaderPath(args[1]);
2994 std::vector<char> shaderBytes;
2996 shaderBytes =
loadSpv(shaderPath);
2997 }
catch (
const mxvk::Exception &e) {
2998 out << std::format(
"{}: failed to load shader '{}': {}", cmd, shaderPath, e.
text());
3002 if (shaderBytes.empty()) {
3003 out << std::format(
"{}: shader '{}' is empty", cmd, shaderPath);
3007 if (cmd ==
"set_wall") {
3008 wallFragSpv = shaderPath;
3009 rawWallRenderer.reloadFragShader(shaderBytes);
3010 out << std::format(
"Wall shader reloaded from {}", shaderPath);
3011 }
else if (cmd ==
"set_floor") {
3012 floorFragSpv = shaderPath;
3013 floorModel.setShaders(
this, modelVertSpv, shaderPath);
3014 out << std::format(
"Floor shader reloaded from {}", shaderPath);
3015 }
else if (cmd ==
"set_pillar") {
3016 pillarFragSpv = shaderPath;
3017 rawPillarRenderer.reloadFragShader(shaderBytes);
3018 out << std::format(
"Pillar shader reloaded from {}", shaderPath);
3019 }
else if (cmd ==
"set_object") {
3020 objectFragSpv = shaderPath;
3021 saturnModel.setShaders(
this, modelVertSpv, shaderPath);
3022 birdModel.setShaders(
this, modelVertSpv, shaderPath);
3023 out << std::format(
"Object shader reloaded from {}", shaderPath);
3024 }
else if (cmd ==
"set_bullet") {
3025 bulletFragSpv = shaderPath;
3026 bulletModel.setShaders(
this, modelVertSpv, shaderPath);
3027 out << std::format(
"Bullet shader reloaded from {}", shaderPath);
3030 logEnv(std::format(
"command: {} shader={}", cmd, shaderPath));
3034 if (cmd ==
"list_shaders") {
3035 const std::array<std::pair<std::string_view, std::string>, 11> shaders{{
3036 {
"wall.frag", resolveShaderPath(
"wall.frag.spv")},
3037 {
"floor.frag", resolveShaderPath(
"floor.frag.spv")},
3038 {
"pillar.frag", resolveShaderPath(
"pillar.frag.spv")},
3039 {
"object.frag", resolveShaderPath(
"object.frag.spv")},
3040 {
"bullet.frag", resolveShaderPath(
"bullet.frag.spv")},
3041 {
"particle.frag", resolveShaderPath(
"particle.frag.spv")},
3042 {
"particle_points.frag", resolveShaderPath(
"particle_points.frag.spv")},
3043 {
"bubble.frag", resolveShaderPath(
"bubble.frag.spv")},
3044 {
"floor_kale.frag", resolveShaderPath(
"floor_kale.frag.spv")},
3045 {
"floor_swirl.frag", resolveShaderPath(
"floor_swirl.frag.spv")},
3046 {
"floor_twist.frag", resolveShaderPath(
"floor_twist.frag.spv")},
3049 out <<
"Available shaders:\n";
3050 for (
const auto &[name, path] : shaders) {
3051 out << std::format(
" {:<20} {}\n", name, path);
3053 out << std::format(
"Current bindings:\n"
3064 if (!postProcessingShaders.empty()) {
3065 out << std::format(
" post_fx {} of {} {}\n",
3066 postProcessingShaderIndex + 1,
3067 postProcessingShaders.size(),
3068 currentPostProcessingShader());
3077 out <<
"\nWalk debug commands:\n"
3078 <<
" spawn_random [attempts] Spawn player at random valid location\n"
3079 <<
" spawn random [attempts] Alias for spawn_random\n"
3080 <<
" reset Reset all collectibles to active\n"
3081 <<
" add_collectibles [count] Add random collectibles (alias: add_collectables)\n"
3082 <<
" status Print camera/world/debug state\n"
3083 <<
" teleport <x> <y> <z> Teleport player if destination is valid\n"
3084 <<
" clear_bullets Remove all active bullets\n"
3085 <<
" clear_fx Remove all active explosion particles\n"
3086 <<
" set_fps <on|off> Toggle FPS overlay\n"
3087 <<
" set_wall <shader.spv> Reload wall fragment shader\n"
3088 <<
" set_floor <shader.spv> Reload floor fragment shader\n"
3089 <<
" set_pillar <shader.spv> Reload pillar fragment shader\n"
3090 <<
" set_object <shader.spv> Reload object fragment shader\n"
3091 <<
" set_bullet <shader.spv> Reload bullet fragment shader\n"
3092 <<
" list_shaders Print available shaders and current bindings\n"
3093 <<
" R/T Select previous/next post-processing shader\n"
3094 <<
" regen_world [seed] Regenerate maze, pillars, and collectibles";
3097 void logEnv(
const std::string &message) {
3098 print(std::format(
"[walk] {}", message), {255, 100, 255, 255});
3101 [[nodiscard]]
static std::string
trimLine(std::string value) {
3102 auto begin = value.begin();
3103 while (begin != value.end() && std::isspace(
static_cast<unsigned char>(*begin)) != 0) {
3107 auto end = value.end();
3108 while (end != begin && std::isspace(
static_cast<unsigned char>(*(end - 1))) != 0) {
3112 return std::string(begin, end);
3115 [[nodiscard]]
static std::string
joinPath(
const std::string &base,
const std::string &file) {
3116 const std::filesystem::path filePath(file);
3117 if (base.empty() || filePath.is_absolute()) {
3118 return filePath.string();
3120 return (std::filesystem::path(base) / filePath).string();
3123 [[nodiscard]]
static std::string resolvePostProcessingShaderEntry(
const std::string &shaderPath,
const std::string &entry) {
3124 const std::filesystem::path entryPath(entry);
3125 if (entryPath.is_absolute() || entryPath.extension() ==
".spv") {
3126 return joinPath(shaderPath, entry);
3129 std::filesystem::path spvEntry = entryPath.parent_path() /
"spv" / entryPath.stem();
3130 spvEntry.replace_extension(
".spv");
3131 const std::filesystem::path spvPath = std::filesystem::path(shaderPath) / spvEntry;
3132 if (std::filesystem::exists(spvPath)) {
3133 return spvPath.string();
3136 std::filesystem::path siblingEntry = entryPath;
3137 siblingEntry.replace_extension(
".spv");
3138 const std::filesystem::path siblingSpvPath = std::filesystem::path(shaderPath) / siblingEntry;
3139 if (std::filesystem::exists(siblingSpvPath)) {
3140 return siblingSpvPath.string();
3143 return joinPath(shaderPath, entry);
3146 void loadPostProcessingShaderIndex(
const std::string &shaderPath) {
3147 postProcessingShaderPath = shaderPath;
3148 postProcessingShaders.clear();
3149 postProcessingShaderIndex = 0;
3151 if (postProcessingShaderPath.empty()) {
3156 const std::string indexPath =
joinPath(postProcessingShaderPath,
"index.txt");
3157 std::ifstream input(indexPath);
3158 if (!input.is_open()) {
3159 throw mxvk::Exception(
"walk_post: failed to open post-processing shader index: " + indexPath);
3163 while (std::getline(input, line)) {
3164 const size_t comment = line.find(
'#');
3165 if (comment != std::string::npos) {
3166 line.resize(comment);
3169 const std::string entry =
trimLine(line);
3170 if (entry.empty()) {
3174 const std::string shaderFile = resolvePostProcessingShaderEntry(postProcessingShaderPath, entry);
3175 if (std::filesystem::path(shaderFile).extension() !=
".spv") {
3176 throw mxvk::Exception(
"walk_post: post-processing shader entry is not SPIR-V: " + entry);
3178 if (!std::filesystem::exists(shaderFile)) {
3179 throw mxvk::Exception(
"walk_post: post-processing shader listed in index.txt was not found: " + shaderFile);
3182 postProcessingShaders.push_back(shaderFile);
3185 if (postProcessingShaders.empty()) {
3186 throw mxvk::Exception(
"walk_post: post-processing shader index did not list any shaders: " + indexPath);
3189 logEnv(std::format(
"loaded {} post-processing shader(s) from {}", postProcessingShaders.size(), indexPath));
3192 void setPostProcessingShaderIndex(
const int index) {
3193 if (postProcessingShaders.empty()) {
3194 postProcessingShaderIndex = 0;
3198 const int shaderCount =
static_cast<int>(postProcessingShaders.size());
3199 postProcessingShaderIndex = index % shaderCount;
3200 if (postProcessingShaderIndex < 0) {
3201 postProcessingShaderIndex += shaderCount;
3205 [[nodiscard]]
const std::string ¤tPostProcessingShader()
const {
3206 return postProcessingShaders[
static_cast<std::size_t
>(postProcessingShaderIndex)];
3209 void applyPostProcessingShaderSelection() {
3210 if (postProcessingShaders.empty()) {
3214 if (postProcessingSprite !=
nullptr) {
3215 postProcessingSprite->setFragmentShaderPath(currentPostProcessingShader());
3216 postProcessingFrameCount = 0;
3217 postProcessingStartTime = std::chrono::steady_clock::now();
3218 previousPostProcessingTime = postProcessingStartTime;
3220 logEnv(std::format(
"post-processing shader {} of {}: {}",
3221 postProcessingShaderIndex + 1,
3222 postProcessingShaders.size(),
3223 currentPostProcessingShader()));
3228 if (postProcessingSprite !=
nullptr) {
3229 postProcessingSprite->enableExtendedUBO();
3230 postProcessingFrameCount = 0;
3231 postProcessingStartTime = std::chrono::steady_clock::now();
3232 previousPostProcessingTime = postProcessingStartTime;
3235 logEnv(std::format(
"post-processing shader {} of {}: {}",
3236 postProcessingShaderIndex + 1,
3237 postProcessingShaders.size(),
3238 currentPostProcessingShader()));
3241 void selectPostProcessingShader(
const int direction) {
3242 if (postProcessingShaders.empty()) {
3246 setPostProcessingShaderIndex(postProcessingShaderIndex + direction);
3250 applyPostProcessingShaderSelection();
3253 void updatePostProcessingShaderUniforms(
const float deltaTime) {
3254 if (postProcessingSprite ==
nullptr || postProcessingShaders.empty()) {
3258 const auto now = std::chrono::steady_clock::now();
3259 const float elapsed = std::chrono::duration<float>(now - postProcessingStartTime).count();
3260 const float frameDelta = std::max(deltaTime, std::chrono::duration<float>(now - previousPostProcessingTime).count());
3261 previousPostProcessingTime = now;
3262 ++postProcessingFrameCount;
3264 float mouseX = 0.0f;
3265 float mouseY = 0.0f;
3266 const SDL_MouseButtonFlags mouseButtons = SDL_GetMouseState(&mouseX, &mouseY);
3267 const float mousePressed = (mouseButtons & SDL_BUTTON_LMASK) != 0U ? 1.0f : 0.0f;
3268 const float frameRate = frameDelta > 0.0001f ? (1.0f / frameDelta) : 0.0f;
3271 postProcessingSprite->setMouseState(mouseX, mouseY, mousePressed, mousePressed);
3272 postProcessingSprite->setUniform0(1.0f, 1.0f, 1.0f, 0.0f);
3273 postProcessingSprite->setUniform1(frameDelta, 0.0f, 0.0f, frameRate);
3274 postProcessingSprite->setUniform2(
static_cast<float>(postProcessingFrameCount), elapsed, 48000.0f, 0.0f);
3275 postProcessingSprite->setUniform3(0.0f, 0.0f, 0.0f, 0.0f);
3283 [[nodiscard]] std::string resolveShaderPath(
const std::string &name)
const {
3284 const std::string runtimePath = shaderRoot +
"/" + name;
3285 if (std::filesystem::exists(runtimePath)) {
3291 [[nodiscard]]
static std::string toLowerCopy(std::string value) {
3292 std::transform(value.begin(), value.end(), value.begin(), [](
const unsigned char ch) {
3293 return static_cast<char>(std::tolower(ch));
3298 [[nodiscard]]
static int parseIntOrDefault(
const std::string &text,
const int fallback) {
3299 int value = fallback;
3300 const auto begin = text.data();
3301 const auto end = text.data() + text.size();
3302 const auto [ptr, ec] = std::from_chars(begin, end, value);
3303 if (ec != std::errc{} || ptr != end) {
3309 [[nodiscard]]
static bool tryParseFloat(
const std::string &text,
float &outValue) {
3312 const float value = std::stof(text, &parsed);
3313 if (parsed != text.size()) {
3323 bool sampleNavigablePoint(
const float y,
const float radius, glm::vec3 &outPoint,
const int maxAttempts) {
3324 float minX = -50.0f;
3326 float minZ = -50.0f;
3329 bool haveBounds =
false;
3330 for (
const WallSegment &wall : world.walls()) {
3332 minX = std::min(wall.start.x, wall.end.x);
3333 maxX = std::max(wall.start.x, wall.end.x);
3334 minZ = std::min(wall.start.z, wall.end.z);
3335 maxZ = std::max(wall.start.z, wall.end.z);
3338 minX = std::min(minX, std::min(wall.start.x, wall.end.x));
3339 maxX = std::max(maxX, std::max(wall.start.x, wall.end.x));
3340 minZ = std::min(minZ, std::min(wall.start.z, wall.end.z));
3341 maxZ = std::max(maxZ, std::max(wall.start.z, wall.end.z));
3346 outPoint = world.startPosition();
3351 const float margin = std::max(0.8f, radius + 0.5f);
3357 if (minX > maxX || minZ > maxZ) {
3358 outPoint = world.startPosition();
3363 std::uniform_real_distribution<float> distX(minX, maxX);
3364 std::uniform_real_distribution<float> distZ(minZ, maxZ);
3365 for (
int i = 0; i < maxAttempts; ++i) {
3366 const glm::vec3 candidate{distX(rng), y, distZ(rng)};
3367 if (!world.checkWallCollision(candidate, radius) && !world.checkPillarCollision(candidate, radius)) {
3368 outPoint = candidate;
3376 [[nodiscard]]
static const char *collectibleTypeName(Collectible::Type type)
noexcept {
3377 return type == Collectible::Type::Saturn ?
"saturn" :
"bird";
3380 void loadModel(mxvk::VKAbstractModel &model,
3381 const std::string &modelPath,
3382 const std::string &textureManifest,
3383 const std::string &textureBase,
3384 const std::string &vertSpv,
3385 const std::string &fragSpv,
3386 bool backfaceCulling =
false) {
3387 logEnv(std::format(
"loading model '{}'", modelPath));
3388 model.
load(
this, modelPath, textureManifest, textureBase, 1.0f);
3391 logEnv(std::format(
"model ready '{}'", modelPath));
3394 void cleanupModels() {
3395 floorModel.cleanup(
this);
3396 rawWallRenderer.cleanup(
this);
3397 rawPillarRenderer.cleanup(
this);
3399 saturnModel.cleanup(
this);
3400 birdModel.cleanup(
this);
3401 blasterModel.cleanup(
this);
3402 bulletModel.cleanup(
this);
3405 [[nodiscard]] uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties)
const {
3406 VkPhysicalDeviceMemoryProperties memProperties{};
3408 for (uint32_t i = 0; i < memProperties.memoryTypeCount; ++i) {
3409 if ((typeFilter & (1u << i)) != 0u && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) {
3413 throw mxvk::Exception(
"walk: failed to find suitable Vulkan memory type for point particles");
3416 void initializePointParticles() {
3418 throw mxvk::Exception(
"walk: render resources unavailable for point particles");
3421 destroyPointParticles();
3423 VkBufferCreateInfo bufferInfo{};
3424 bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
3425 bufferInfo.size = maxPointVertices *
sizeof(ParticlePointVertex);
3426 bufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
3427 bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
3428 if (vkCreateBuffer(
getDevice(), &bufferInfo,
nullptr, &pointVertexBuffer) != VK_SUCCESS) {
3429 throw mxvk::Exception(
"walk: failed to create point particle vertex buffer");
3432 VkMemoryRequirements memReq{};
3433 vkGetBufferMemoryRequirements(
getDevice(), pointVertexBuffer, &memReq);
3434 VkMemoryAllocateInfo allocInfo{};
3435 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
3436 allocInfo.allocationSize = memReq.size;
3437 allocInfo.memoryTypeIndex = findMemoryType(memReq.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
3438 if (vkAllocateMemory(
getDevice(), &allocInfo,
nullptr, &pointVertexMemory) != VK_SUCCESS) {
3439 throw mxvk::Exception(
"walk: failed to allocate point particle vertex memory");
3441 if (vkBindBufferMemory(
getDevice(), pointVertexBuffer, pointVertexMemory, 0) != VK_SUCCESS) {
3442 throw mxvk::Exception(
"walk: failed to bind point particle vertex memory");
3444 if (vkMapMemory(
getDevice(), pointVertexMemory, 0, bufferInfo.size, 0, &pointVertexMapped) != VK_SUCCESS) {
3445 throw mxvk::Exception(
"walk: failed to map point particle vertex memory");
3448 rebuildPointParticlePipeline();
3450 destroyPointParticles();
3455 void rebuildPointParticlePipeline() {
3456 if (pointPipeline != VK_NULL_HANDLE) {
3457 vkDestroyPipeline(
getDevice(), pointPipeline,
nullptr);
3458 pointPipeline = VK_NULL_HANDLE;
3460 if (pointPipelineLayout != VK_NULL_HANDLE) {
3461 vkDestroyPipelineLayout(
getDevice(), pointPipelineLayout,
nullptr);
3462 pointPipelineLayout = VK_NULL_HANDLE;
3469 const std::vector<char> vertBytes =
loadSpv(pointParticleVertSpv);
3470 const std::vector<char> fragBytes =
loadSpv(pointParticleFragSpv);
3474 VkPipelineShaderStageCreateInfo vertStage{};
3475 vertStage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
3476 vertStage.stage = VK_SHADER_STAGE_VERTEX_BIT;
3477 vertStage.module = vertModule;
3478 vertStage.pName =
"main";
3480 VkPipelineShaderStageCreateInfo fragStage{};
3481 fragStage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
3482 fragStage.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
3483 fragStage.module = fragModule;
3484 fragStage.pName =
"main";
3485 const std::array<VkPipelineShaderStageCreateInfo, 2> stages = {vertStage, fragStage};
3487 VkVertexInputBindingDescription binding{};
3488 binding.binding = 0;
3489 binding.stride =
sizeof(ParticlePointVertex);
3490 binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
3492 std::array<VkVertexInputAttributeDescription, 3> attrs{};
3493 attrs[0] = {0, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(ParticlePointVertex, pos)};
3494 attrs[1] = {1, 0, VK_FORMAT_R32G32B32A32_SFLOAT, offsetof(ParticlePointVertex, color)};
3495 attrs[2] = {2, 0, VK_FORMAT_R32_SFLOAT, offsetof(ParticlePointVertex, size)};
3497 VkPipelineVertexInputStateCreateInfo vertexInput{};
3498 vertexInput.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
3499 vertexInput.vertexBindingDescriptionCount = 1;
3500 vertexInput.pVertexBindingDescriptions = &binding;
3501 vertexInput.vertexAttributeDescriptionCount =
static_cast<uint32_t
>(attrs.size());
3502 vertexInput.pVertexAttributeDescriptions = attrs.data();
3504 VkPipelineInputAssemblyStateCreateInfo inputAssembly{};
3505 inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
3506 inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_POINT_LIST;
3508 const std::array<VkDynamicState, 2> dynamicStates = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
3509 VkPipelineDynamicStateCreateInfo dynamicInfo{};
3510 dynamicInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
3511 dynamicInfo.dynamicStateCount =
static_cast<uint32_t
>(dynamicStates.size());
3512 dynamicInfo.pDynamicStates = dynamicStates.data();
3514 VkPipelineViewportStateCreateInfo viewportState{};
3515 viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
3516 viewportState.viewportCount = 1;
3517 viewportState.scissorCount = 1;
3519 VkPipelineRasterizationStateCreateInfo rasterizer{};
3520 rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
3521 rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
3522 rasterizer.cullMode = VK_CULL_MODE_NONE;
3523 rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE;
3524 rasterizer.lineWidth = 1.0f;
3526 VkPipelineMultisampleStateCreateInfo multisample{};
3527 multisample.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
3528 multisample.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
3530 VkPipelineDepthStencilStateCreateInfo depthStencil{};
3531 depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
3532 depthStencil.depthTestEnable = VK_FALSE;
3533 depthStencil.depthWriteEnable = VK_FALSE;
3534 depthStencil.depthCompareOp = VK_COMPARE_OP_LESS_OR_EQUAL;
3536 VkPipelineColorBlendAttachmentState blendAttachment{};
3537 blendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
3538 blendAttachment.blendEnable = VK_TRUE;
3539 blendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
3540 blendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE;
3541 blendAttachment.colorBlendOp = VK_BLEND_OP_ADD;
3542 blendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
3543 blendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
3544 blendAttachment.alphaBlendOp = VK_BLEND_OP_ADD;
3546 VkPipelineColorBlendStateCreateInfo colorBlend{};
3547 colorBlend.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
3548 colorBlend.attachmentCount = 1;
3549 colorBlend.pAttachments = &blendAttachment;
3551 VkPushConstantRange pushRange{};
3552 pushRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
3553 pushRange.offset = 0;
3554 pushRange.size =
sizeof(glm::mat4);
3556 VkPipelineLayoutCreateInfo layoutInfo{};
3557 layoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
3558 layoutInfo.pushConstantRangeCount = 1;
3559 layoutInfo.pPushConstantRanges = &pushRange;
3560 if (vkCreatePipelineLayout(
getDevice(), &layoutInfo,
nullptr, &pointPipelineLayout) != VK_SUCCESS) {
3561 vkDestroyShaderModule(
getDevice(), fragModule,
nullptr);
3562 vkDestroyShaderModule(
getDevice(), vertModule,
nullptr);
3563 throw mxvk::Exception(
"walk: failed to create point particle pipeline layout");
3568 VkPipelineRenderingCreateInfo renderingInfo{};
3569 renderingInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO;
3570 renderingInfo.colorAttachmentCount = 1;
3571 renderingInfo.pColorAttachmentFormats = &colorFormat;
3572 if (depthFormat != VK_FORMAT_UNDEFINED) {
3573 renderingInfo.depthAttachmentFormat = depthFormat;
3576 VkGraphicsPipelineCreateInfo pipelineInfo{};
3577 pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
3578 pipelineInfo.pNext = &renderingInfo;
3579 pipelineInfo.stageCount =
static_cast<uint32_t
>(stages.size());
3580 pipelineInfo.pStages = stages.data();
3581 pipelineInfo.pVertexInputState = &vertexInput;
3582 pipelineInfo.pInputAssemblyState = &inputAssembly;
3583 pipelineInfo.pViewportState = &viewportState;
3584 pipelineInfo.pRasterizationState = &rasterizer;
3585 pipelineInfo.pMultisampleState = &multisample;
3586 pipelineInfo.pDepthStencilState = &depthStencil;
3587 pipelineInfo.pColorBlendState = &colorBlend;
3588 pipelineInfo.pDynamicState = &dynamicInfo;
3589 pipelineInfo.layout = pointPipelineLayout;
3590 pipelineInfo.renderPass = VK_NULL_HANDLE;
3592 if (vkCreateGraphicsPipelines(
getDevice(), VK_NULL_HANDLE, 1, &pipelineInfo,
nullptr, &pointPipeline) != VK_SUCCESS) {
3593 vkDestroyShaderModule(
getDevice(), fragModule,
nullptr);
3594 vkDestroyShaderModule(
getDevice(), vertModule,
nullptr);
3595 throw mxvk::Exception(
"walk: failed to create point particle graphics pipeline");
3598 vkDestroyShaderModule(
getDevice(), fragModule,
nullptr);
3599 vkDestroyShaderModule(
getDevice(), vertModule,
nullptr);
3602 void destroyPointParticles() {
3603 if (pointPipeline != VK_NULL_HANDLE) {
3604 vkDestroyPipeline(
getDevice(), pointPipeline,
nullptr);
3605 pointPipeline = VK_NULL_HANDLE;
3607 if (pointPipelineLayout != VK_NULL_HANDLE) {
3608 vkDestroyPipelineLayout(
getDevice(), pointPipelineLayout,
nullptr);
3609 pointPipelineLayout = VK_NULL_HANDLE;
3611 if (pointVertexMapped !=
nullptr) {
3612 vkUnmapMemory(
getDevice(), pointVertexMemory);
3613 pointVertexMapped =
nullptr;
3615 if (pointVertexBuffer != VK_NULL_HANDLE) {
3616 vkDestroyBuffer(
getDevice(), pointVertexBuffer,
nullptr);
3617 pointVertexBuffer = VK_NULL_HANDLE;
3619 if (pointVertexMemory != VK_NULL_HANDLE) {
3620 vkFreeMemory(
getDevice(), pointVertexMemory,
nullptr);
3621 pointVertexMemory = VK_NULL_HANDLE;
3625 void renderPointParticles(VkCommandBuffer cmd,
const glm::mat4 &view,
const glm::mat4 &proj) {
3626 if (pointPipeline == VK_NULL_HANDLE || pointPipelineLayout == VK_NULL_HANDLE || pointVertexMapped ==
nullptr) {
3630 std::vector<ParticlePointVertex> vertices{};
3631 vertices.reserve(2048);
3633 for (
const Projectile &bullet : bullets) {
3634 if (!bullet.active) {
3637 for (
const Projectile::TrailPoint &point : bullet.trail) {
3638 const float life = glm::clamp(point.lifetime / point.maxLifetime, 0.0f, 1.0f);
3639 const float fade = 1.0f - life;
3640 vertices.push_back({point.position, glm::vec4(1.0f, 0.2f, 0.0f, fade * 0.8f), 12.0f});
3644 for (
const ExplosionParticle &particle : explosionParticles) {
3645 if (!particle.active) {
3648 const float life = glm::clamp(particle.lifetime / particle.maxLifetime, 0.0f, 1.0f);
3649 const float fade = 1.0f - life;
3650 const float sizePx = glm::clamp(particle.size * 320.0f, 12.0f, 160.0f);
3651 vertices.push_back({particle.position, glm::vec4(particle.color, fade), sizePx});
3654 if (vertices.empty()) {
3658 if (vertices.size() > maxPointVertices) {
3659 vertices.resize(maxPointVertices);
3661 std::memcpy(pointVertexMapped, vertices.data(), vertices.size() *
sizeof(ParticlePointVertex));
3663 const VkBuffer vb = pointVertexBuffer;
3664 const VkDeviceSize offset = 0;
3665 vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pointPipeline);
3666 vkCmdBindVertexBuffers(cmd, 0, 1, &vb, &offset);
3667 const glm::mat4 vp = proj * view;
3668 vkCmdPushConstants(cmd, pointPipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0,
sizeof(glm::mat4), &vp);
3669 vkCmdDraw(cmd,
static_cast<uint32_t
>(vertices.size()), 1, 0, 0);
3672 bool openGamepad(SDL_JoystickID
id) {
3673 if (gamepad !=
nullptr && gamepadId ==
id) {
3676 if (gamepad !=
nullptr) {
3677 SDL_CloseGamepad(gamepad);
3681 gamepad = SDL_OpenGamepad(
id);
3682 if (gamepad ==
nullptr) {
3683 logEnv(std::format(
"failed to open gamepad id={}",
static_cast<int>(
id)));
3687 const char *padName = SDL_GetGamepadName(gamepad);
3688 logEnv(std::format(
"gamepad connected: id={} name='{}'",
3689 static_cast<int>(
id),
3690 padName !=
nullptr ? padName :
"unknown"));
3694 void tryOpenFirstGamepad() {
3695 if (gamepad !=
nullptr) {
3699 SDL_JoystickID *ids = SDL_GetGamepads(&count);
3700 if (ids ==
nullptr || count <= 0) {
3701 if (ids !=
nullptr) {
3706 openGamepad(ids[0]);
3710 [[nodiscard]]
static glm::mat4 composeNormalizedModel(
const mxvk::VKAbstractModel &model,
const glm::mat4 &world) {
3711 glm::mat4 transform = world;
3712 transform = transform * glm::scale(glm::mat4(1.0f), glm::vec3(model.
modelRenderScale()));
3713 transform = transform * glm::translate(glm::mat4(1.0f), model.
modelCenterOffset());
3720 [[nodiscard]]
static glm::mat4 composeRecenteredModel(
const mxvk::VKAbstractModel &model,
const glm::mat4 &world) {
3724 void renderModel(VkCommandBuffer cmd,
3725 uint32_t imageIndex,
3726 mxvk::VKAbstractModel &model,
3727 const glm::mat4 &world,
3728 const glm::mat4 &view,
3729 const glm::mat4 &proj,
3730 const glm::vec4 &fx,
3731 bool autoNormalize =
true) {
3732 mxvk::UniformBufferObject ubo{};
3733 ubo.
model = autoNormalize ? composeNormalizedModel(model, world)
3734 : composeRecenteredModel(model, world);
3739 model.
render(cmd, imageIndex,
false);
3742 void renderRawModel(VkCommandBuffer cmd,
3743 uint32_t imageIndex,
3744 mxvk::VKAbstractModel &model,
3745 const glm::mat4 &world,
3746 const glm::mat4 &view,
3747 const glm::mat4 &proj,
3748 const glm::vec4 &fx) {
3749 mxvk::UniformBufferObject ubo{};
3755 model.
render(cmd, imageIndex,
false);
3758 void updateCameraVectors() {
3759 glm::vec3 front(0.0f);
3760 front.x = std::cos(glm::radians(yaw)) * std::cos(glm::radians(pitch));
3761 front.y = std::sin(glm::radians(pitch));
3762 front.z = std::sin(glm::radians(yaw)) * std::cos(glm::radians(pitch));
3763 cameraFront = glm::normalize(front);
3766 void buildCameraBasis(glm::vec3 &forward, glm::vec3 &right, glm::vec3 &up)
const {
3767 forward = cameraFront;
3768 if (glm::length(forward) <= 1e-5f) {
3769 forward = glm::vec3(0.0f, 0.0f, -1.0f);
3771 forward = glm::normalize(forward);
3774 right = glm::cross(forward, glm::vec3(0.0f, 1.0f, 0.0f));
3775 if (glm::length(right) <= 1e-5f) {
3776 right = glm::vec3(1.0f, 0.0f, 0.0f);
3778 right = glm::normalize(right);
3781 up = glm::cross(right, forward);
3782 if (glm::length(up) <= 1e-5f) {
3783 up = glm::vec3(0.0f, 1.0f, 0.0f);
3785 up = glm::normalize(up);
3789 [[nodiscard]] glm::vec3 blasterMuzzleTipPosition()
const {
3790 glm::vec3 forward(0.0f);
3791 glm::vec3 right(0.0f);
3793 buildCameraBasis(forward, right, up);
3794 return cameraPos + (forward * 0.55f) + (right * 0.18f) - (up * 0.12f);
3797 [[nodiscard]] glm::vec3 projectileSpawnPosition()
const {
3798 glm::vec3 forward(0.0f);
3799 glm::vec3 right(0.0f);
3801 buildCameraBasis(forward, right, up);
3802 constexpr float projectileForwardOffset = 0.015f;
3803 return blasterMuzzleTipPosition() + (forward * projectileForwardOffset);
3806 [[nodiscard]] glm::mat4 blasterWorldTransform()
const {
3807 glm::vec3 forward(0.0f);
3808 glm::vec3 right(0.0f);
3810 buildCameraBasis(forward, right, up);
3812 constexpr float blasterScale = 0.45f;
3813 constexpr glm::vec3 localMuzzle(0.95f, 0.09f, 0.0f);
3814 const glm::vec3 desiredMuzzle = blasterMuzzleTipPosition();
3815 const glm::vec3 origin = desiredMuzzle - (forward * (localMuzzle.x * blasterScale)) - (up * (localMuzzle.y * blasterScale)) - (right * (localMuzzle.z * blasterScale));
3817 glm::mat4 world(1.0f);
3818 world[0] = glm::vec4(forward * blasterScale, 0.0f);
3819 world[1] = glm::vec4(up * blasterScale, 0.0f);
3820 world[2] = glm::vec4(right * blasterScale, 0.0f);
3821 world[3] = glm::vec4(origin, 1.0f);
3825 [[nodiscard]]
float viewDistanceInDirection(
const glm::vec3 &origin,
const glm::vec3 &direction)
const {
3826 const glm::vec3 dir = glm::normalize(glm::vec3(direction.x, 0.0f, direction.z));
3827 constexpr float maxDistance = 14.0f;
3828 constexpr float step = 0.35f;
3829 constexpr float probeRadius = 0.30f;
3830 for (
float d = step; d <= maxDistance; d += step) {
3831 const glm::vec3 point = origin + (dir * d);
3832 if (world.checkWallCollision(point, probeRadius) || world.checkPillarCollision(point, probeRadius)) {
3839 [[nodiscard]]
float chooseBestSpawnYaw(
const glm::vec3 &origin)
const {
3840 constexpr float pi = 3.14159265358979323846f;
3841 constexpr int sampleCount = 48;
3842 float bestDistance = -1.0f;
3843 float bestYaw = yaw;
3844 for (
int i = 0; i < sampleCount; ++i) {
3845 const float angle = (-pi) + (2.0f * pi *
static_cast<float>(i) /
static_cast<float>(sampleCount));
3846 const glm::vec3 dir(std::cos(angle), 0.0f, std::sin(angle));
3847 const float dist = viewDistanceInDirection(origin, dir);
3848 if (dist > bestDistance) {
3849 bestDistance = dist;
3850 bestYaw = glm::degrees(angle);
3856 void updatePlayer(
float deltaTime) {
3857 const bool *keys = SDL_GetKeyboardState(
nullptr);
3858 glm::vec3 horizontalFront = glm::normalize(glm::vec3(cameraFront.x, 0.0f, cameraFront.z));
3859 if (glm::length(horizontalFront) < 0.0001f) {
3860 horizontalFront = glm::vec3(0.0f, 0.0f, -1.0f);
3862 const glm::vec3 right = glm::normalize(glm::cross(horizontalFront, glm::vec3(0.0f, 1.0f, 0.0f)));
3864 glm::vec3 desired = cameraPos;
3865 bool sprint = keys[SDL_SCANCODE_LSHIFT] != 0;
3866 if (gamepad !=
nullptr && SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_LEFT_STICK)) {
3869 const float cameraSpeed = 0.2f;
3870 const float speed = sprint ? cameraSpeed * 2.0f : cameraSpeed;
3871 const float frameScale = deltaTime * 60.0f;
3872 const float moveStep = speed * frameScale;
3874 if (keys[SDL_SCANCODE_W]) {
3875 desired += horizontalFront * moveStep;
3877 if (keys[SDL_SCANCODE_S]) {
3878 desired -= horizontalFront * moveStep;
3880 if (keys[SDL_SCANCODE_A]) {
3881 desired -= right * moveStep;
3883 if (keys[SDL_SCANCODE_D]) {
3884 desired += right * moveStep;
3887 if (gamepad !=
nullptr) {
3888 const Sint16 leftX = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_LEFTX);
3889 const Sint16 leftY = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_LEFTY);
3890 if (std::abs(leftX) > stickDeadZone) {
3891 desired += moveStep * (
static_cast<float>(leftX) / 32768.0f) * right;
3893 if (std::abs(leftY) > stickDeadZone) {
3894 desired -= moveStep * (
static_cast<float>(leftY) / 32768.0f) * horizontalFront;
3897 const Sint16 rightX = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHTX);
3898 const Sint16 rightY = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHTY);
3899 if (std::abs(rightX) > stickDeadZone || std::abs(rightY) > stickDeadZone) {
3900 yaw += (
static_cast<float>(rightX) / 32768.0f) * controllerLookSensitivity;
3901 pitch -= (
static_cast<float>(rightY) / 32768.0f) * controllerLookSensitivity;
3902 pitch = glm::clamp(pitch, -89.0f, 89.0f);
3903 updateCameraVectors();
3907 constexpr float playerRadius = 0.5f;
3908 constexpr float cameraStandOff = 0.18f;
3909 const float collisionRadius = playerRadius + cameraStandOff;
3910 const auto isBlocked = [
this, collisionRadius](
const glm::vec3 &position) {
3911 return world.checkWallCollision(position, collisionRadius) || world.checkPillarCollision(position, collisionRadius);
3914 if (!isBlocked(desired)) {
3915 cameraPos = desired;
3919 glm::vec3 tryX = cameraPos;
3921 if (!isBlocked(tryX)) {
3922 cameraPos.x = tryX.x;
3925 glm::vec3 tryZ = cameraPos;
3927 if (!isBlocked(tryZ)) {
3928 cameraPos.z = tryZ.z;
3932 const bool crouch = keys[SDL_SCANCODE_LCTRL] != 0;
3933 const float minHeight = crouch ? 0.8f : 1.7f;
3934 if (keys[SDL_SCANCODE_SPACE] && cameraPos.y <= minHeight + 0.01f) {
3935 jumpVelocity = 0.3f;
3938 cameraPos.y += jumpVelocity * deltaTime * 60.0f;
3939 jumpVelocity -= gravity * deltaTime * 60.0f;
3940 if (cameraPos.y < minHeight) {
3941 cameraPos.y = minHeight;
3942 jumpVelocity = 0.0f;
3946 void fireProjectile() {
3947 emitMuzzleParticles();
3949 Projectile bullet{};
3950 bullet.position = projectileSpawnPosition();
3951 bullet.direction = glm::normalize(cameraFront);
3952 bullets.push_back(bullet);
3953 logEnv(std::format(
"projectile fired from ({:.2f}, {:.2f}, {:.2f}) dir=({:.2f}, {:.2f}, {:.2f}) active_bullets={}",
3963 void emitMuzzleParticles() {
3964 glm::vec3 forward(0.0f);
3965 glm::vec3 right(0.0f);
3967 buildCameraBasis(forward, right, up);
3969 const glm::vec3 muzzle = blasterMuzzleTipPosition();
3970 std::uniform_real_distribution<float> lateralJitter(-0.20f, 0.20f);
3971 std::uniform_real_distribution<float> verticalJitter(-0.12f, 0.12f);
3972 std::uniform_real_distribution<float> speedDist(8.0f, 26.0f);
3973 std::uniform_real_distribution<float> lifeDist(0.06f, 0.16f);
3974 std::uniform_real_distribution<float> warmDist(0.75f, 1.0f);
3976 constexpr int particleCount = 24;
3977 for (
int i = 0; i < particleCount; ++i) {
3978 ExplosionParticle p{};
3979 p.position = muzzle + (forward * 0.01f);
3981 glm::vec3 dir = forward + (right * lateralJitter(rng)) + (up * verticalJitter(rng));
3982 if (glm::length(dir) <= 1e-5f) {
3985 dir = glm::normalize(dir);
3988 const float speed = speedDist(rng);
3989 p.velocity = dir * speed;
3990 p.color = glm::vec3(warmDist(rng), warmDist(rng) * 0.7f, warmDist(rng) * 0.18f);
3991 p.maxLifetime = lifeDist(rng);
3992 p.size = 0.035f + (speed * 0.003f);
3993 explosionParticles.push_back(p);
3997 void updateProjectiles(
float deltaTime) {
3998 for (
size_t bulletIndex = 0; bulletIndex < bullets.size(); ++bulletIndex) {
3999 Projectile &bullet = bullets[bulletIndex];
4004 const glm::vec3 previous = bullet.position;
4005 const glm::vec3 displacement = bullet.direction * bullet.speed * deltaTime;
4006 bullet.position += displacement;
4008 bullet.distanceTraveled += glm::length(displacement);
4009 bullet.trailTimer += deltaTime;
4010 if (bullet.trailTimer >= 0.02f) {
4011 Projectile::TrailPoint point{};
4012 point.position = bullet.position;
4013 bullet.trail.push_back(point);
4014 bullet.trailTimer = 0.0f;
4016 for (Projectile::TrailPoint &point : bullet.trail) {
4017 point.lifetime += deltaTime;
4020 std::remove_if(bullet.trail.begin(), bullet.trail.end(), [](
const Projectile::TrailPoint &point) {
4021 return point.lifetime >= point.maxLifetime;
4023 bullet.trail.end());
4025 size_t collectibleIndex = 0;
4026 glm::vec3 collectibleImpact{0.0f};
4027 if (lineHitCollectible(previous, bullet.position, collectibleIndex, collectibleImpact)) {
4028 createExplosion(collectibleImpact, 5000,
false);
4029 const Collectible::Type hitType = world.collectibles()[collectibleIndex].type;
4030 const bool removed = deactivateCollectibleAt(collectibleIndex);
4031 resolveCollectibleClusters(2.0f, 3);
4036 logEnv(std::format(
"bullet {} hit {} collectible {} at ({:.2f}, {:.2f}, {:.2f}); destroyed={}",
4038 collectibleTypeName(hitType),
4040 collectibleImpact.x,
4041 collectibleImpact.y,
4042 collectibleImpact.z,
4047 const ProjectileTraceHit segmentHit = traceProjectileSegment(previous, bullet.position);
4048 if (segmentHit.type != ProjectileHitType::None) {
4050 if (segmentHit.type == ProjectileHitType::Floor) {
4051 createExplosion(glm::vec3(segmentHit.impact.x, 0.0f, segmentHit.impact.z), 1500,
true);
4053 logEnv(std::format(
"bullet {} hit floor at ({:.2f}, {:.2f}, {:.2f})",
4055 segmentHit.impact.x,
4057 segmentHit.impact.z));
4061 createExplosion(segmentHit.impact, 1500,
true);
4063 logEnv(std::format(
"bullet {} hit {} at ({:.2f}, {:.2f}, {:.2f})",
4065 (segmentHit.type == ProjectileHitType::Pillar) ?
"pillar" :
"wall",
4066 segmentHit.impact.x,
4067 segmentHit.impact.y,
4068 segmentHit.impact.z));
4072 if (bullet.
lifetime >= bullet.maxLifetime) {
4074 logEnv(std::format(
"bullet {} expired after {:.2f}s", bulletIndex, bullet.
lifetime));
4078 if (bullet.distanceTraveled >= bullet.maxDistance) {
4080 logEnv(std::format(
"bullet {} faded after traveling {:.2f} units", bulletIndex, bullet.distanceTraveled));
4084 bullets.erase(std::remove_if(bullets.begin(), bullets.end(), [](
const Projectile &b) { return !b.active; }), bullets.end());
4087 void updateCollectibles(
float deltaTime) {
4088 for (Collectible &obj : world.collectibles()) {
4092 obj.rotation.y += obj.rotationSpeed * deltaTime;
4093 if (obj.rotation.y > 360.0f) {
4094 obj.rotation.y -= 360.0f;
4098 collectibleClusterResolveTimer += deltaTime;
4099 if (collectibleClusterResolveTimer >= 0.75f) {
4100 collectibleClusterResolveTimer = 0.0f;
4101 resolveCollectibleClusters(2.0f, 2);
4105 void createExplosion(
const glm::vec3 &position,
int requestedCount,
bool isRed) {
4106 if (requestedCount <= 0) {
4110 constexpr float pi = 3.14159265358979323846f;
4111 std::uniform_real_distribution<float> speedDist(3.0f, 15.0f);
4112 std::uniform_real_distribution<float> angleDist(0.0f, 2.0f * pi);
4113 std::uniform_real_distribution<float> elevationDist(-(pi / 6.0f), pi / 3.0f);
4114 std::uniform_real_distribution<float> colorDist(0.7f, 1.0f);
4116 const int count = std::min(requestedCount * 2, 800);
4117 logEnv(std::format(
"explosion at ({:.2f}, {:.2f}, {:.2f}) particles={} style={}",
4122 isRed ?
"impact" :
"collectible"));
4123 for (
int i = 0; i < count; ++i) {
4124 ExplosionParticle p{};
4125 p.position = position;
4126 const float theta = angleDist(rng);
4127 const float phi = elevationDist(rng);
4128 const float v = speedDist(rng);
4129 p.velocity = glm::vec3(v * std::cos(phi) * std::cos(theta), v * std::sin(phi), v * std::cos(phi) * std::sin(theta));
4131 p.color = glm::vec3(colorDist(rng), colorDist(rng) * 0.3f, colorDist(rng) * 0.1f);
4133 p.color = glm::vec3(colorDist(rng), colorDist(rng) * 0.7f, colorDist(rng) * 0.2f);
4135 p.maxLifetime = 0.55f;
4136 p.size = 0.08f + (v * 0.010f);
4137 explosionParticles.push_back(p);
4141 void updateExplosions(
float deltaTime) {
4142 for (ExplosionParticle &particle : explosionParticles) {
4143 if (!particle.active) {
4146 particle.position += particle.velocity * deltaTime;
4147 particle.velocity.y -= 9.8f * deltaTime;
4149 for (
const PillarInstance &pillar : world.pillars()) {
4150 const glm::vec2 particle2d(particle.position.x, particle.position.z);
4151 const glm::vec2 pillar2d(pillar.position.x, pillar.position.z);
4152 const float distance = glm::length(particle2d - pillar2d);
4153 if (distance < pillar.radius && particle.position.y > 0.0f && particle.position.y < pillar.height) {
4154 glm::vec2 normal(1.0f, 0.0f);
4155 if (distance > 0.00001f) {
4156 normal = glm::normalize(particle2d - pillar2d);
4158 const glm::vec2 vel2d(particle.velocity.x, particle.velocity.z);
4159 const glm::vec2 reflected = vel2d - 2.0f * glm::dot(vel2d, normal) * normal;
4160 particle.velocity.x = reflected.x * 0.5f;
4161 particle.velocity.z = reflected.y * 0.5f;
4162 const glm::vec2 correction = normal * (pillar.radius - distance + 0.1f);
4163 particle.position.x += correction.x;
4164 particle.position.z += correction.y;
4168 for (
const WallSegment &wall : world.walls()) {
4169 glm::vec3 wallDir = wall.end - wall.start;
4170 const float wallLength = glm::length(wallDir);
4171 if (wallLength < 0.0001f) {
4174 wallDir = glm::normalize(wallDir);
4175 const glm::vec3 toStart = particle.position - wall.start;
4176 float projection = glm::dot(toStart, wallDir);
4177 projection = glm::clamp(projection, 0.0f, wallLength);
4178 glm::vec3 closest = wall.start + wallDir * projection;
4179 closest.y = particle.position.y;
4180 const float distance = glm::length(particle.position - closest);
4181 if (distance < 0.5f && particle.position.y >= 0.0f && particle.position.y <= wall.height) {
4182 glm::vec3 normal(1.0f, 0.0f, 0.0f);
4183 if (distance > 0.0001f) {
4184 normal = glm::normalize(particle.position - closest);
4186 particle.velocity = glm::reflect(particle.velocity, normal) * 0.5f;
4187 particle.position += normal * 0.2f;
4191 if (particle.position.y < 0.0f) {
4192 particle.position.y = 0.0f;
4193 particle.velocity.y = -particle.velocity.y * 0.3f;
4194 particle.velocity.x *= 0.8f;
4195 particle.velocity.z *= 0.8f;
4198 particle.lifetime += deltaTime;
4199 particle.size *= 0.98f;
4200 if (particle.lifetime >= particle.maxLifetime) {
4201 particle.active =
false;
4205 explosionParticles.erase(
4206 std::remove_if(explosionParticles.begin(), explosionParticles.end(), [](
const ExplosionParticle &p) {
4209 explosionParticles.end());
4212 [[nodiscard]]
bool lineHitWall(
const glm::vec3 &from,
const glm::vec3 &to, glm::vec3 &impactOut)
const {
4213 const glm::vec3 dir = to - from;
4214 constexpr float bulletRadius = 0.015f;
4215 const float travel = glm::length(dir);
4216 if (travel <= 1e-8f) {
4220 constexpr float sampleStride = 0.05f;
4221 const int steps = std::max(1,
static_cast<int>(std::ceil(travel / sampleStride)));
4222 float previousT = 0.0f;
4223 for (
int i = 0; i <= steps; ++i) {
4224 const float t =
static_cast<float>(i) /
static_cast<float>(steps);
4225 const glm::vec3 point = from + (dir * t);
4226 if (pointHitsWall3D(point, bulletRadius)) {
4227 float lo = previousT;
4229 for (
int iter = 0; iter < 10; ++iter) {
4230 const float mid = 0.5f * (lo + hi);
4231 const glm::vec3 midPoint = from + (dir * mid);
4232 if (pointHitsWall3D(midPoint, bulletRadius)) {
4238 impactOut = from + (dir * hi);
4246 [[nodiscard]]
bool lineHitPillar(
const glm::vec3 &from,
const glm::vec3 &to, glm::vec3 &impactOut)
const {
4247 const glm::vec3 dir = to - from;
4248 constexpr float bulletRadius = 0.015f;
4249 const float travel = glm::length(dir);
4250 if (travel <= 1e-8f) {
4254 constexpr float sampleStride = 0.05f;
4255 const int steps = std::max(1,
static_cast<int>(std::ceil(travel / sampleStride)));
4256 float previousT = 0.0f;
4257 for (
int i = 0; i <= steps; ++i) {
4258 const float t =
static_cast<float>(i) /
static_cast<float>(steps);
4259 const glm::vec3 point = from + (dir * t);
4260 if (pointHitsPillar3D(point, bulletRadius)) {
4261 float lo = previousT;
4263 for (
int iter = 0; iter < 10; ++iter) {
4264 const float mid = 0.5f * (lo + hi);
4265 const glm::vec3 midPoint = from + (dir * mid);
4266 if (pointHitsPillar3D(midPoint, bulletRadius)) {
4272 impactOut = from + (dir * hi);
4280 [[nodiscard]]
bool lineHitCollectible(
const glm::vec3 &from,
const glm::vec3 &to,
size_t &indexOut, glm::vec3 &impactOut)
const {
4281 const glm::vec3 dir = to - from;
4282 const float dirLen2 = glm::dot(dir, dir);
4283 constexpr float bulletRadius = 0.015f;
4284 if (dirLen2 <= 1e-8f) {
4290 size_t bestIndex = 0;
4292 const std::vector<Collectible> &collectibles = world.collectibles();
4293 for (
size_t i = 0; i < collectibles.size(); ++i) {
4294 const Collectible &obj = collectibles[i];
4302 if (obj.type == Collectible::Type::Bird) {
4303 const glm::vec3 halfExtents(obj.radius + bulletRadius);
4304 const glm::vec3 center = obj.position + obj.hitCenterOffset;
4305 const glm::vec3 boxMin = center - halfExtents;
4306 const glm::vec3 boxMax = center + halfExtents;
4310 bool slabMiss =
false;
4312 for (
int axis = 0; axis < 3; ++axis) {
4313 const float origin = from[axis];
4314 const float delta = dir[axis];
4315 const float minB = boxMin[axis];
4316 const float maxB = boxMax[axis];
4318 if (std::abs(delta) <= 1e-8f) {
4319 if (origin < minB || origin > maxB) {
4326 float t0 = (minB - origin) / delta;
4327 float t1 = (maxB - origin) / delta;
4332 tMin = std::max(tMin, t0);
4333 tMax = std::min(tMax, t1);
4345 const glm::vec3 center = obj.position + obj.hitCenterOffset;
4346 const glm::vec3 m = from - center;
4347 const float a = dirLen2;
4348 const float b = 2.0f * glm::dot(m, dir);
4349 const float hitRadius = obj.radius + bulletRadius;
4350 const float c = glm::dot(m, m) - (hitRadius * hitRadius);
4351 const float discriminant = (b * b) - (4.0f * a * c);
4352 if (discriminant >= 0.0f) {
4353 const float sqrtD = std::sqrt(discriminant);
4354 const float invDen = 1.0f / (2.0f * a);
4355 const float t0 = (-b - sqrtD) * invDen;
4356 const float t1 = (-b + sqrtD) * invDen;
4357 if (t0 >= 0.0f && t0 <= 1.0f) {
4360 }
else if (t1 >= 0.0f && t1 <= 1.0f) {
4367 if (hit && tHit >= 0.0f && tHit <= 1.0f && tHit < bestT) {
4378 indexOut = bestIndex;
4379 impactOut = from + (dir * bestT);
4383 [[nodiscard]]
bool pointHitsWall3D(
const glm::vec3 &point,
float radius)
const {
4384 const float halfThickness = (world.wallThickness() * 0.5f) + radius;
4385 const float halfThicknessSq = halfThickness * halfThickness;
4386 for (
const WallSegment &wall : world.walls()) {
4387 if (point.y < 0.0f || point.y > wall.height) {
4391 const glm::vec2 start(wall.start.x, wall.start.z);
4392 const glm::vec2 end(wall.end.x, wall.end.z);
4393 const glm::vec2 seg = end - start;
4394 const float segLen2 = glm::dot(seg, seg);
4395 if (segLen2 <= 1e-8f) {
4399 const glm::vec2 p(point.x, point.z);
4400 const glm::vec2 toPoint = p - start;
4401 const float t = glm::clamp(glm::dot(toPoint, seg) / segLen2, 0.0f, 1.0f);
4402 const glm::vec2 closest = start + (seg * t);
4403 const glm::vec2 d = p - closest;
4404 if (glm::dot(d, d) <= halfThicknessSq) {
4411 [[nodiscard]]
bool pointHitsPillar3D(
const glm::vec3 &point,
float radius)
const {
4412 for (
const PillarInstance &pillar : world.pillars()) {
4413 if (point.y < 0.0f || point.y > pillar.height) {
4417 const glm::vec2 p(point.x, point.z);
4418 const glm::vec2 c(pillar.position.x, pillar.position.z);
4419 const float hitRadius = pillar.radius + radius;
4420 const glm::vec2 d = p - c;
4421 if (glm::dot(d, d) <= (hitRadius * hitRadius)) {
4428 [[nodiscard]]
float birdGroundYForScale(
float scale)
const {
4429 const glm::vec3 extent = birdModel.modelAxisExtent();
4430 const glm::vec3 centerOffset = birdModel.modelCenterOffset();
4431 const float modelMinY = -centerOffset.y - (extent.y * 0.5f);
4432 const float clampedScale = std::max(scale, 0.0001f);
4433 return std::max(0.0f, -modelMinY * clampedScale);
4436 [[nodiscard]]
float birdHitHalfSideForScale(
float scale)
const {
4437 const glm::vec3 extent = birdModel.modelAxisExtent();
4438 const float modelSide = std::max({extent.x, extent.y, extent.z, 0.0001f});
4439 const float clampedScale = std::max(scale, 0.0001f);
4440 return 0.5f * modelSide * clampedScale;
4443 [[nodiscard]]
float saturnHitRadiusForScale(
float scale)
const {
4444 const glm::vec3 extent = saturnModel.modelAxisExtent();
4445 const float modelDiameter = std::max({extent.x, extent.y, extent.z, 0.0001f});
4446 const float clampedScale = std::max(scale, 0.0001f);
4447 return 0.5f * modelDiameter * clampedScale;
4450 [[nodiscard]] glm::vec3 saturnHitCenterOffsetForScale(
float scale)
const {
4451 const glm::vec3 centerOffset = saturnModel.modelCenterOffset();
4452 const float clampedScale = std::max(scale, 0.0001f);
4453 return glm::vec3(-centerOffset.x * clampedScale,
4454 -centerOffset.y * clampedScale,
4455 -centerOffset.z * clampedScale);
4458 [[nodiscard]] glm::vec3 birdHitCenterOffsetForScale(
float scale)
const {
4459 const glm::vec3 centerOffset = birdModel.modelCenterOffset();
4460 const float clampedScale = std::max(scale, 0.0001f);
4461 return glm::vec3(0.0f, -centerOffset.y * clampedScale, 0.0f);
4464 [[nodiscard]]
float birdSpawnClearanceRadiusForScale(
float scale)
const {
4465 const glm::vec3 extent = birdModel.modelAxisExtent();
4466 const glm::vec3 centerOffset = birdModel.modelCenterOffset();
4467 const float clampedScale = std::max(scale, 0.0001f);
4469 const float halfXFromOrigin = (extent.x * 0.5f) + std::abs(centerOffset.x);
4470 const float halfZFromOrigin = (extent.z * 0.5f) + std::abs(centerOffset.z);
4471 const float horizontalRadius = std::max(halfXFromOrigin, halfZFromOrigin) * clampedScale;
4472 return horizontalRadius + 0.05f;
4475 [[nodiscard]]
float placementRadiusForCollectible(
const Collectible &obj)
const {
4476 if (obj.type == Collectible::Type::Bird) {
4477 return std::max(obj.radius, birdSpawnClearanceRadiusForScale(obj.scale.x));
4482 void normalizeCollectiblesToModel() {
4483 for (Collectible &obj : world.collectibles()) {
4484 if (obj.type == Collectible::Type::Bird) {
4485 obj.radius = birdHitHalfSideForScale(obj.scale.x);
4486 obj.hitCenterOffset = birdHitCenterOffsetForScale(obj.scale.x);
4487 obj.position.y = birdGroundYForScale(obj.scale.x);
4489 obj.radius = saturnHitRadiusForScale(obj.scale.x);
4490 obj.hitCenterOffset = saturnHitCenterOffsetForScale(obj.scale.x);
4494 resolveCollectibleEnvironmentCollisions();
4495 resolveCollectibleOverlaps();
4496 resolveCollectibleClusters(2.0f, 5);
4499 [[nodiscard]]
bool overlapsCollectibleAt(
const glm::vec3 &candidate,
4502 bool includeInactive)
const {
4503 const std::vector<Collectible> &collectibles = world.collectibles();
4504 for (
size_t i = 0; i < collectibles.size(); ++i) {
4505 if (i == ignoreIndex) {
4508 const Collectible &other = collectibles[i];
4509 if (!includeInactive && !other.active) {
4513 const float separation = std::max(5.0f, other.radius + radius + 0.2f);
4514 if (glm::length(other.position - candidate) < separation) {
4521 bool relocateCollectible(
size_t index,
float minMoveDistance,
int maxAttempts) {
4522 std::vector<Collectible> &collectibles = world.collectibles();
4523 if (index >= collectibles.size()) {
4527 Collectible &obj = collectibles[index];
4528 const glm::vec3 oldPosition = obj.position;
4529 const float y = (obj.type == Collectible::Type::Bird) ? birdGroundYForScale(obj.scale.x) : 2.5f;
4530 const float placementRadius = placementRadiusForCollectible(obj);
4532 for (
int attempt = 0; attempt < maxAttempts; ++attempt) {
4533 glm::vec3 candidate{};
4534 if (!sampleNavigablePoint(y, placementRadius, candidate, 4)) {
4537 if (glm::length(candidate - oldPosition) < minMoveDistance) {
4540 if (overlapsCollectibleAt(candidate, obj.radius, index,
false)) {
4544 obj.position = candidate;
4551 void resolveCollectibleEnvironmentCollisions() {
4552 std::vector<Collectible> &collectibles = world.collectibles();
4553 for (
size_t i = 0; i < collectibles.size(); ++i) {
4554 if (!collectibles[i].
active) {
4558 const float placementRadius = placementRadiusForCollectible(collectibles[i]);
4559 if (!world.checkWallCollision(collectibles[i].position, placementRadius) &&
4560 !world.checkPillarCollision(collectibles[i].position, placementRadius)) {
4564 relocateCollectible(i, 2.0f, 512);
4568 void resolveCollectibleOverlaps() {
4569 std::vector<Collectible> &collectibles = world.collectibles();
4570 for (
size_t i = 0; i < collectibles.size(); ++i) {
4571 if (!collectibles[i].
active) {
4574 if (!overlapsCollectibleAt(collectibles[i].position, collectibles[i].radius, i,
false)) {
4577 relocateCollectible(i, 1.5f, 320);
4581 void resolveCollectibleClusters(
float minVisualSeparation,
int passes) {
4582 if (minVisualSeparation <= 0.0f || passes <= 0) {
4586 std::vector<Collectible> &collectibles = world.collectibles();
4587 const float minVisualSeparationSq = minVisualSeparation * minVisualSeparation;
4588 for (
int pass = 0; pass < passes; ++pass) {
4589 bool movedAny =
false;
4590 for (
size_t i = 0; i < collectibles.size(); ++i) {
4591 if (!collectibles[i].
active) {
4595 for (
size_t j = i + 1; j < collectibles.size(); ++j) {
4596 if (!collectibles[j].
active) {
4600 const glm::vec3 delta = collectibles[j].position - collectibles[i].position;
4601 if (glm::dot(delta, delta) >= minVisualSeparationSq) {
4605 if (relocateCollectible(j, minVisualSeparation, 512)) {
4617 void disperseNearbyCollectibles(
const glm::vec3 ¢er,
float radius,
size_t ignoreIndex) {
4618 std::vector<Collectible> &collectibles = world.collectibles();
4619 for (
size_t i = 0; i < collectibles.size(); ++i) {
4620 if (i == ignoreIndex) {
4623 if (!collectibles[i].
active) {
4626 if (glm::length(collectibles[i].position - center) > radius) {
4630 relocateCollectible(i, std::max(3.0f, radius), 256);
4634 [[nodiscard]]
bool deactivateCollectibleAt(
size_t index) {
4635 std::vector<Collectible> &collectibles = world.collectibles();
4636 if (index >= collectibles.size()) {
4640 Collectible &obj = collectibles[index];
4649 std::string assetRoot;
4650 std::string shaderRoot;
4651 std::string modelRoot;
4654 mxvk::VKAbstractModel floorModel{};
4655 RawWallRenderer rawWallRenderer{};
4656 RawPillarRenderer rawPillarRenderer{};
4657 mxvk::VKAbstractModel saturnModel{};
4658 mxvk::VKAbstractModel birdModel{};
4659 mxvk::VKAbstractModel blasterModel{};
4660 mxvk::VKAbstractModel bulletModel{};
4662 VkPipelineLayout pointPipelineLayout = VK_NULL_HANDLE;
4663 VkPipeline pointPipeline = VK_NULL_HANDLE;
4664 VkBuffer pointVertexBuffer = VK_NULL_HANDLE;
4665 VkDeviceMemory pointVertexMemory = VK_NULL_HANDLE;
4666 void *pointVertexMapped =
nullptr;
4667 size_t maxPointVertices = 200000;
4668 std::string pointParticleVertSpv{};
4669 std::string pointParticleFragSpv{};
4670 std::string modelVertSpv{};
4671 std::string pillarVertSpv{};
4672 std::string wallFragSpv{};
4673 std::string floorFragSpv{};
4674 std::string pillarFragSpv{};
4675 std::string objectFragSpv{};
4676 std::string bulletFragSpv{};
4677 std::string postProcessingShaderPath{};
4678 std::vector<std::string> postProcessingShaders{};
4679 mxvk::VK_Sprite *postProcessingSprite =
nullptr;
4680 int postProcessingShaderIndex = 0;
4681 uint32_t postProcessingFrameCount = 0;
4682 std::chrono::steady_clock::time_point postProcessingStartTime{std::chrono::steady_clock::now()};
4683 std::chrono::steady_clock::time_point previousPostProcessingTime{postProcessingStartTime};
4685 std::vector<Projectile> bullets{};
4686 std::vector<ExplosionParticle> explosionParticles{};
4687 std::mt19937
rng{std::random_device{}()};
4689 glm::vec3 cameraPos{0.0f, 1.7f, 0.0f};
4690 glm::vec3 cameraFront{0.0f, 0.0f, -1.0f};
4693 bool mouseCapture =
true;
4694 bool firstMouse =
true;
4695 bool suppressProjectileOnNextLeftDown =
false;
4696 bool showFps =
true;
4697 float mouseSensitivity = 0.15f;
4699 float jumpVelocity = 0.0f;
4700 float gravity = 0.015f;
4701 float collectibleClusterResolveTimer = 0.0f;
4702 uint32_t destroyedCount = 0;
4704 SDL_Gamepad *gamepad =
nullptr;
4705 SDL_JoystickID gamepadId = 0;
4706 int stickDeadZone = 8000;
4707 float controllerLookSensitivity = 2.0f;
4709 std::chrono::steady_clock::time_point lastTick{std::chrono::steady_clock::now()};
4720 std::cerr << std::format(
"mxvk: Exception: {}\n", e.
text());
4721 return EXIT_FAILURE;
4723 std::cerr << std::format(
"mxvk: Argument Exception: {}\n", e.
text());
4724 return EXIT_FAILURE;
4727 return EXIT_SUCCESS;
Lightweight, header-only, template command-line argument parser.
Arguments proc_args(int &argc, char **argv)
Parse standard libmx2 command-line options from main()'s argv.
Exception thrown by Argz::proc() on unrecognised or malformed options.
void setBackfaceCulling(bool enabled)
Enable or disable backface culling for this model pipeline.
void updateUBO(uint32_t imageIndex, const UniformBufferObject &ubo)
Update one per-frame UBO payload.
float modelRenderScale() const
Access the computed render scale used for normalization.
void load(VK_Window *window, const std::string &modelPath, const std::string &textureManifestPath, const std::string &textureBasePath, float scale=1.0f)
Load mesh/texture resources and build Vulkan state.
void setShaders(VK_Window *window, const std::string &vertSpv, const std::string &fragSpv)
Configure custom shader paths and rebuild pipelines.
glm::vec3 modelCenterOffset() const
Access the computed center offset used for normalization.
void render(VkCommandBuffer cmd, uint32_t imageIndex, bool wireframe=false) const
Record draw commands for this model.
virtual void appendConsoleHelp(std::ostream &out) const
Append app-specific help lines to the console help output.
virtual bool handleConsoleCommand(const std::vector< std::string > &args, std::ostream &out)
Handle app-specific console commands.
void event(SDL_Event &e) override
Handle one SDL event.
void print(const std::string &text, SDL_Color col={255, 255, 255, 255})
VK_IOWindow(const std::string &path, const std::string &title, const int width, const int height, const bool fullscreen, const bool enableVsync=false)
Main Vulkan window wrapper for MXVK.
VkExtent2D getSwapchainExtent() const noexcept
Get the current swapchain extent.
void loop()
Run the main event/render loop.
VkDevice getDevice() const noexcept
Get the Vulkan logical device handle.
SDL_Window * getSDLWindow() const noexcept
Get the underlying SDL window handle.
void setClearColor(float r, float g, float b, float a=1.0f)
Set the per-frame color attachment clear color.
void exit()
Request loop termination.
VK_Sprite * attachPostProcessingShader(const std::string &fragmentShaderPath, float p1=0.0f, float p2=0.0f, float p3=0.0f, float p4=0.0f)
Attach a full-screen post-processing fragment shader.
bool ensureRenderResources()
Ensure deferred render resources are initialized.
VkFormat getDepthFormat() const noexcept
Get the depth format used for dynamic rendering attachments.
void setFont(const std::string &fontPath, int fontSize=24)
Set the active text-render font.
void setPostProcessingEnabled(bool enabled)
void printText(const std::string &text, int x, int y, const SDL_Color &col)
Queue a text string for rendering during the current frame.
static std::vector< char > loadSpv(const std::string &path)
Load a SPIR-V file from disk.
void setPostProcessingShaderParams(float p1=0.0f, float p2=0.0f, float p3=0.0f, float p4=0.0f)
Set the post-processing shader params passed as a vec4 push constant.
VkPhysicalDevice getPhysicalDevice() const noexcept
Get the Vulkan physical device handle.
VkFormat getSwapchainFormat() const noexcept
Get the swapchain color format.
const std::vector< PillarInstance > & pillars() const noexcept
bool checkCollectibleCollision(const glm::vec3 &point, size_t &indexOut) const
std::vector< Collectible > & collectibles() noexcept
glm::vec3 startPosition() const noexcept
bool checkWallCollision(const glm::vec3 &position, float radius) const
float wallThickness() const noexcept
bool checkPillarCollision(const glm::vec3 &position, float playerRadius) const
const std::vector< WallSegment > & walls() const noexcept
const std::vector< Collectible > & collectibles() const noexcept
int activeCollectibles() const
void generate(uint32_t seed)
glm::vec3 randomPointInCell(int cellX, int cellZ, float objectRadius, float y, std::mt19937 &rng, float margin) const
void load(mxvk::VK_Window *targetWindow, const std::string &textureManifestPath, const std::string &textureBasePath, const std::vector< char > &vertSpv, const std::vector< char > &fragSpv)
void resize(mxvk::VK_Window *targetWindow)
void reloadFragShader(const std::vector< char > &newFragSpv)
Hot-swap the fragment shader without rebuilding geometry or descriptors.
void render(VkCommandBuffer cmd, uint32_t imageIndex, const std::vector< PillarInstance > &pillars, const glm::mat4 &view, const glm::mat4 &proj, const glm::vec4 &fx)
void cleanup(mxvk::VK_Window *targetWindow)
void reloadFragShader(const std::vector< char > &newFragSpv)
Hot-swap the fragment shader without rebuilding geometry or descriptors.
void resize(mxvk::VK_Window *targetWindow)
void load(mxvk::VK_Window *targetWindow, const std::string &textureManifestPath, const std::string &textureBasePath, const std::vector< char > &vertexShaderSpv, const std::vector< char > &fragmentShaderSpv)
void render(VkCommandBuffer cmd, uint32_t imageIndex, const std::vector< WallSegment > &walls, float wallThickness, const glm::mat4 &view, const glm::mat4 &proj, const glm::vec4 &fx)
void cleanup(mxvk::VK_Window *targetWindow)
void event(SDL_Event &e) override
Handle one SDL event.
void onRecordCustomRendering(VkCommandBuffer cmd, uint32_t imageIndex) override
Optional hook for derived classes to record extra draw commands.
void console_proc() override
WalkWindow(const Arguments &args)
void onSwapchainRecreated() override
Called after swapchain and render resources are recreated.
void console_event(SDL_Event &e) override
High-level model wrapper integrated with MXVK dynamic rendering.
PNG image loading and saving utilities via SDL3.
std::string trimLine(const std::string &text)
std::string joinPath(const std::string &base, const std::string &file)
Utilities for loading and saving PNG images.
VkShaderModule create_shader_module(VkDevice device, const std::vector< char > &spv_bytes)
Create a shader module from SPIR-V bytecode.
SDL_Surface * LoadPNG(const char *file)
Load a PNG file into an SDL_Surface.
std::default_random_engine & rng()
Returns the thread-local random number engine used by simulation helpers.
std::atomic< bool > active
Plain data structure returned by proc_args() with all common libmx2 CLI options.
std::string shaderPath
Optional SPV shader folder path (-S / --shader-path).
bool fullscreen
Whether fullscreen mode was requested.
int height
Viewport height in pixels (default: 720).
int width
Viewport width in pixels (default: 1280).
int shader_index
Optional initial shader entry index.
glm::vec3 hitCenterOffset
std::vector< TrailPoint > trail