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 && std::abs(delta.y) <= halfSide && std::abs(delta.z) <= halfSide) {
186 if (glm::length(center - point) < collectible.radius) {
194 [[nodiscard]] glm::vec3
randomPointInCell(
int cellX,
int cellZ,
float objectRadius,
float y, std::mt19937 &rng,
float margin)
const {
195 const float pad = objectRadius + wallThicknessValue * 0.5f + margin;
196 const float x0 = -size +
static_cast<float>(cellX) * cellSize;
197 const float z0 = -size +
static_cast<float>(cellZ) * cellSize;
198 const float x1 = x0 + cellSize;
199 const float z1 = z0 + cellSize;
201 float minX = x0 + pad;
202 float maxX = x1 - pad;
203 float minZ = z0 + pad;
204 float maxZ = z1 - pad;
206 minX = maxX = (x0 + x1) * 0.5f;
209 minZ = maxZ = (z0 + z1) * 0.5f;
212 std::uniform_real_distribution<float> distX(minX, maxX);
213 std::uniform_real_distribution<float> distZ(minZ, maxZ);
214 return glm::vec3(distX(rng), y, distZ(rng));
217 [[nodiscard]]
float wallThickness() const noexcept {
return wallThicknessValue; }
221 bool visited =
false;
222 std::array<bool, 4>
walls{
true,
true,
true,
true};
225 void generateMaze(std::mt19937 &rng) {
226 const int gridX = mazeGridX;
227 const int gridZ = mazeGridZ;
228 cellSize = (size * 2.0f) /
static_cast<float>(gridX);
230 auto indexFor = [gridX](
int x,
int z) {
return z * gridX + x; };
232 std::vector<Cell> grid(
static_cast<size_t>(gridX * gridZ));
233 std::vector<std::pair<int, int>> stack;
234 stack.emplace_back(0, 0);
235 grid[
static_cast<size_t>(indexFor(0, 0))].visited =
true;
237 while (!stack.empty()) {
238 const auto [x, z] = stack.back();
239 std::vector<int> dirs;
240 if (z > 0 && !grid[
static_cast<size_t>(indexFor(x, z - 1))].visited) {
243 if (x < (gridX - 1) && !grid[
static_cast<size_t>(indexFor(x + 1, z))].visited) {
246 if (z < (gridZ - 1) && !grid[
static_cast<size_t>(indexFor(x, z + 1))].visited) {
249 if (x > 0 && !grid[
static_cast<size_t>(indexFor(x - 1, z))].visited) {
258 std::uniform_int_distribution<size_t> pick(0, dirs.size() - 1U);
259 const int d = dirs[pick(rng)];
272 grid[
static_cast<size_t>(indexFor(x, z))].
walls[
static_cast<size_t>(d)] =
false;
273 grid[
static_cast<size_t>(indexFor(nx, nz))].
walls[
static_cast<size_t>((d + 2) % 4)] =
false;
274 grid[
static_cast<size_t>(indexFor(nx, nz))].visited =
true;
275 stack.emplace_back(nx, nz);
278 wallSegments.clear();
279 for (
int z = 0; z < gridZ; ++z) {
280 for (
int x = 0; x < gridX; ++x) {
281 const float cx = -size +
static_cast<float>(x) * cellSize;
282 const float cz = -size +
static_cast<float>(z) * cellSize;
285 const float x1 = cx + cellSize;
286 const float z1 = cz + cellSize;
287 const Cell &cell = grid[
static_cast<size_t>(indexFor(x, z))];
290 wallSegments.push_back({glm::vec3(x0, 0.0f, z0), glm::vec3(x1, 0.0f, z0), wallHeight});
293 wallSegments.push_back({glm::vec3(x0, 0.0f, z1), glm::vec3(x0, 0.0f, z0), wallHeight});
295 if (x == (gridX - 1) && cell.walls[1]) {
296 wallSegments.push_back({glm::vec3(x1, 0.0f, z0), glm::vec3(x1, 0.0f, z1), wallHeight});
298 if (z == (gridZ - 1) && cell.walls[2]) {
299 wallSegments.push_back({glm::vec3(x1, 0.0f, z1), glm::vec3(x0, 0.0f, z1), wallHeight});
303 mergeContiguousWalls();
305 const float playerRadius = 0.5f;
308 startPositionValue =
randomPointInCell(0, 0, playerRadius, eyeHeight, rng, 0.5f);
310 for (
int z = 0; z < mazeGridZ; ++z) {
311 for (
int x = 0; x < mazeGridX; ++x) {
312 startPositionValue =
randomPointInCell(x, z, playerRadius, eyeHeight, rng, 0.5f);
323 void mergeContiguousWalls() {
324 if (wallSegments.empty()) {
328 struct NormalizedWall {
329 bool horizontal =
false;
330 float constantAxis = 0.0f;
331 float startAxis = 0.0f;
332 float endAxis = 0.0f;
336 constexpr float epsilon = 0.0001f;
337 constexpr float adjacencyEpsilon = 0.001f;
338 constexpr float quantizeScale = 1000.0f;
340 const auto quantize = [](
float value) {
return static_cast<int>(std::lround(value * quantizeScale)); };
342 std::vector<NormalizedWall> normalized;
343 normalized.reserve(wallSegments.size());
344 for (
const WallSegment &wall : wallSegments) {
345 const bool horizontal = std::abs(wall.start.z - wall.end.z) <= epsilon;
347 const float x0 = std::min(wall.start.x, wall.end.x);
348 const float x1 = std::max(wall.start.x, wall.end.x);
349 normalized.push_back({
true, wall.start.z, x0, x1, wall.height});
351 const float z0 = std::min(wall.start.z, wall.end.z);
352 const float z1 = std::max(wall.start.z, wall.end.z);
353 normalized.push_back({
false, wall.start.x, z0, z1, wall.height});
357 std::sort(normalized.begin(), normalized.end(), [&quantize](
const NormalizedWall &a,
const NormalizedWall &b) {
358 const auto keyA = std::array<int, 3>{a.horizontal ? 1 : 0, quantize(a.constantAxis), quantize(a.height)};
359 const auto keyB = std::array<int, 3>{b.horizontal ? 1 : 0, quantize(b.constantAxis), quantize(b.height)};
363 if (a.startAxis != b.startAxis) {
364 return a.startAxis < b.startAxis;
366 return a.endAxis < b.endAxis;
369 std::vector<WallSegment> merged;
370 merged.reserve(normalized.size());
372 while (index < normalized.size()) {
373 const NormalizedWall first = normalized[index];
374 float runStart = first.startAxis;
375 float runEnd = first.endAxis;
377 size_t next = index + 1;
378 while (next < normalized.size()) {
379 const NormalizedWall &candidate = normalized[next];
380 if (candidate.horizontal != first.horizontal || quantize(candidate.constantAxis) != quantize(first.constantAxis) || quantize(candidate.height) != quantize(first.height)) {
384 if (candidate.startAxis <= (runEnd + adjacencyEpsilon)) {
385 runEnd = std::max(runEnd, candidate.endAxis);
390 if (first.horizontal) {
391 merged.push_back({glm::vec3(runStart, 0.0f, first.constantAxis), glm::vec3(runEnd, 0.0f, first.constantAxis), first.height});
393 merged.push_back({glm::vec3(first.constantAxis, 0.0f, runStart), glm::vec3(first.constantAxis, 0.0f, runEnd), first.height});
395 runStart = candidate.startAxis;
396 runEnd = candidate.endAxis;
400 if (first.horizontal) {
401 merged.push_back({glm::vec3(runStart, 0.0f, first.constantAxis), glm::vec3(runEnd, 0.0f, first.constantAxis), first.height});
403 merged.push_back({glm::vec3(first.constantAxis, 0.0f, runStart), glm::vec3(first.constantAxis, 0.0f, runEnd), first.height});
408 wallSegments.swap(merged);
411 void generatePillars(std::mt19937 &rng) {
412 pillarInstances.clear();
413 std::uniform_real_distribution<float> radiusDist(0.5f, 1.5f);
414 std::uniform_real_distribution<float> heightDist(3.0f, 6.0f);
416 constexpr int targetPillars = 15;
417 constexpr int maxAttempts = targetPillars * 8;
419 for (
int attempt = 0; attempt < maxAttempts && created < targetPillars; ++attempt) {
420 const int cellX =
static_cast<int>(
rng() %
static_cast<uint32_t
>(mazeGridX));
421 const int cellZ =
static_cast<int>(
rng() %
static_cast<uint32_t
>(mazeGridZ));
423 if (cellX == startCellX && cellZ == startCellZ) {
427 pillar.radius = radiusDist(rng);
428 pillar.height = heightDist(rng);
429 pillar.position = randomPointInCell(cellX, cellZ, pillar.radius, 0.0f, rng, 0.3f);
430 if (!checkWallCollision(pillar.position, pillar.radius)) {
431 pillarInstances.push_back(pillar);
437 void generateCollectibles(std::mt19937 &rng) {
438 collectibleItems.clear();
439 const int usableCells = std::max(1, (mazeGridX * mazeGridZ) - 1);
440 const int targetCollectibles = usableCells * collectiblesPerCell;
441 collectibleItems.reserve(
static_cast<size_t>(targetCollectibles));
443 std::vector<Collectible::Type> types;
444 types.reserve(
static_cast<size_t>(targetCollectibles));
445 const int saturnCount = targetCollectibles / 2;
446 for (
int i = 0; i < saturnCount; ++i) {
449 for (
int i = saturnCount; i < targetCollectibles; ++i) {
452 std::shuffle(types.begin(), types.end(), rng);
454 std::uniform_real_distribution<float> saturnScale(0.4f, 0.8f);
455 std::uniform_real_distribution<float> saturnRotSpeed(5.0f, 15.0f);
456 std::uniform_real_distribution<float> birdScale(0.3f, 0.5f);
457 std::uniform_real_distribution<float> birdRotSpeed(20.0f, 60.0f);
460 for (
int cellZ = 0; cellZ < mazeGridZ; ++cellZ) {
461 for (
int cellX = 0; cellX < mazeGridX; ++cellX) {
462 if (cellX == startCellX && cellZ == startCellZ) {
465 for (
int slot = 0; slot < collectiblesPerCell; ++slot) {
466 if (typeIndex >= targetCollectibles) {
470 obj.type = types[
static_cast<size_t>(typeIndex)];
472 const float scale = saturnScale(rng);
473 obj.scale = glm::vec3(scale);
474 obj.rotationSpeed = saturnRotSpeed(rng);
475 obj.radius = 2.0f * scale;
477 const float scale = birdScale(rng);
478 obj.scale = glm::vec3(scale);
479 obj.rotationSpeed = birdRotSpeed(rng);
480 obj.radius = 0.5f * scale;
483 bool foundSpot =
false;
485 for (
int attempt = 0; attempt < 24 && !foundSpot; ++attempt) {
487 const float margin = (attempt < 18) ? 0.45f : 0.10f;
488 const glm::vec3 candidate = randomPointInCell(cellX, cellZ, obj.radius, y, rng, margin);
489 fallback = candidate;
491 bool overlapsOtherCollectible =
false;
492 for (
const Collectible &placed : collectibleItems) {
493 const float separation = std::max(5.0f, placed.radius + obj.radius + 0.2f);
494 if (glm::length(placed.position - candidate) < separation) {
495 overlapsOtherCollectible =
true;
500 if (!overlapsOtherCollectible && !checkWallCollision(candidate, obj.radius) && !checkPillarCollision(candidate, obj.radius)) {
501 obj.position = candidate;
508 obj.position = fallback;
510 collectibleItems.push_back(obj);
517 std::vector<WallSegment> wallSegments{};
518 std::vector<PillarInstance> pillarInstances{};
519 std::vector<Collectible> collectibleItems{};
522 float wallHeight = 5.0f;
523 float wallThicknessValue = 0.5f;
526 int collectiblesPerCell = 1;
527 float cellSize = 0.0f;
528 float eyeHeight = 1.7f;
531 glm::vec3 startPositionValue{0.0f, 1.7f, 0.0f};
534 class RawPillarRenderer {
536 struct PillarVertex {
537 glm::vec3 position{0.0f};
538 glm::vec2 texCoord{0.0f};
539 glm::vec3 normal{0.0f};
542 struct PillarUniforms {
543 glm::mat4 view{1.0f};
544 glm::mat4 proj{1.0f};
548 void load(
mxvk::VK_Window *targetWindow,
const std::string &textureManifestPath,
const std::string &textureBasePath,
const std::vector<char> &vertSpv,
const std::vector<char> &fragSpv) {
549 if (targetWindow ==
nullptr) {
550 throw mxvk::Exception(
"walk: raw pillar renderer requires a valid window");
552 window = targetWindow;
554 fragmentSpv = fragSpv;
556 if (!window->ensureRenderResources()) {
557 throw mxvk::Exception(
"walk: raw pillar renderer requires render resources");
561 loadTexture(textureManifestPath, textureBasePath);
562 createTextureSampler();
563 createDescriptorSetLayout();
564 createUniformBuffers();
565 createDescriptorPool();
566 createDescriptorSets();
571 if (targetWindow ==
nullptr || targetWindow->
getDevice() == VK_NULL_HANDLE) {
575 window = targetWindow;
577 destroyDescriptors();
578 createDescriptorSetLayout();
579 createUniformBuffers();
580 createDescriptorPool();
581 createDescriptorSets();
588 if (window ==
nullptr || window->getDevice() == VK_NULL_HANDLE || newFragSpv.empty()) {
591 vkDeviceWaitIdle(window->getDevice());
592 fragmentSpv = newFragSpv;
598 if (targetWindow ==
nullptr || targetWindow->
getDevice() == VK_NULL_HANDLE) {
602 window = targetWindow;
604 destroyDescriptors();
610 void render(VkCommandBuffer cmd, uint32_t imageIndex,
const std::vector<PillarInstance> &pillars,
const glm::mat4 &view,
const glm::mat4 &proj,
const glm::vec4 &fx) {
611 if (cmd == VK_NULL_HANDLE || pipeline == VK_NULL_HANDLE || pipelineLayout == VK_NULL_HANDLE) {
614 if (imageIndex >= uniformBuffersMapped.size() || descriptorSets.empty() || vertexBuffer == VK_NULL_HANDLE || indexBuffer == VK_NULL_HANDLE) {
619 uniforms.view = view;
620 uniforms.proj = proj;
622 std::memcpy(uniformBuffersMapped[imageIndex], &uniforms,
sizeof(
PillarUniforms));
624 vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
625 vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets[imageIndex], 0,
nullptr);
627 const VkDeviceSize offset = 0;
628 vkCmdBindVertexBuffers(cmd, 0, 1, &vertexBuffer, &offset);
629 vkCmdBindIndexBuffer(cmd, indexBuffer, 0, VK_INDEX_TYPE_UINT32);
635 constexpr float baseSink = 0.02f;
636 glm::mat4
model = glm::translate(glm::mat4(1.0f), glm::vec3(pillar.position.x, pillar.position.y - baseSink, pillar.position.z));
637 model = glm::scale(
model, glm::vec3(pillar.radius, pillar.height, pillar.radius));
638 vkCmdPushConstants(cmd, pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0,
sizeof(glm::mat4), &
model);
639 vkCmdDrawIndexed(cmd, indexCount, 1, 0, 0, 0);
644 [[nodiscard]] uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties)
const {
645 VkPhysicalDeviceMemoryProperties memProperties{};
646 vkGetPhysicalDeviceMemoryProperties(window->getPhysicalDevice(), &memProperties);
647 for (uint32_t i = 0; i < memProperties.memoryTypeCount; ++i) {
648 if ((typeFilter & (1u << i)) != 0u && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) {
652 throw mxvk::Exception(
"walk: failed to find suitable memory type for raw pillar renderer");
655 void createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer &buffer, VkDeviceMemory &bufferMemory)
const {
656 VkBufferCreateInfo bufferInfo{};
657 bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
658 bufferInfo.size = size;
659 bufferInfo.usage = usage;
660 bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
662 if (vkCreateBuffer(window->getDevice(), &bufferInfo,
nullptr, &buffer) != VK_SUCCESS) {
663 throw mxvk::Exception(
"walk: failed to create raw pillar buffer");
666 VkMemoryRequirements requirements{};
667 vkGetBufferMemoryRequirements(window->getDevice(), buffer, &requirements);
669 VkMemoryAllocateInfo allocInfo{};
670 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
671 allocInfo.allocationSize = requirements.size;
674 allocInfo.memoryTypeIndex = findMemoryType(requirements.memoryTypeBits, properties);
675 if (vkAllocateMemory(window->getDevice(), &allocInfo,
nullptr, &bufferMemory) != VK_SUCCESS) {
676 throw mxvk::Exception(
"walk: failed to allocate raw pillar buffer memory");
679 if (vkBindBufferMemory(window->getDevice(), buffer, bufferMemory, 0) != VK_SUCCESS) {
680 throw mxvk::Exception(
"walk: failed to bind raw pillar buffer memory");
683 if (bufferMemory != VK_NULL_HANDLE) {
684 vkFreeMemory(window->getDevice(), bufferMemory,
nullptr);
685 bufferMemory = VK_NULL_HANDLE;
687 if (buffer != VK_NULL_HANDLE) {
688 vkDestroyBuffer(window->getDevice(), buffer,
nullptr);
689 buffer = VK_NULL_HANDLE;
695 void createImage(uint32_t width, uint32_t height, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, VkImage &image, VkDeviceMemory &memory)
const {
696 VkImageCreateInfo imageInfo{};
697 imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
698 imageInfo.imageType = VK_IMAGE_TYPE_2D;
699 imageInfo.extent.width = width;
700 imageInfo.extent.height = height;
701 imageInfo.extent.depth = 1;
702 imageInfo.mipLevels = 1;
703 imageInfo.arrayLayers = 1;
704 imageInfo.format = format;
705 imageInfo.tiling = tiling;
706 imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
707 imageInfo.usage = usage;
708 imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
709 imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
711 if (vkCreateImage(window->getDevice(), &imageInfo,
nullptr, &image) != VK_SUCCESS) {
712 throw mxvk::Exception(
"walk: failed to create raw pillar image");
715 VkMemoryRequirements requirements{};
716 vkGetImageMemoryRequirements(window->getDevice(), image, &requirements);
718 VkMemoryAllocateInfo allocInfo{};
719 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
720 allocInfo.allocationSize = requirements.size;
723 allocInfo.memoryTypeIndex = findMemoryType(requirements.memoryTypeBits, properties);
724 if (vkAllocateMemory(window->getDevice(), &allocInfo,
nullptr, &memory) != VK_SUCCESS) {
725 throw mxvk::Exception(
"walk: failed to allocate raw pillar image memory");
728 if (vkBindImageMemory(window->getDevice(), image, memory, 0) != VK_SUCCESS) {
729 throw mxvk::Exception(
"walk: failed to bind raw pillar image memory");
732 if (memory != VK_NULL_HANDLE) {
733 vkFreeMemory(window->getDevice(), memory,
nullptr);
734 memory = VK_NULL_HANDLE;
736 if (image != VK_NULL_HANDLE) {
737 vkDestroyImage(window->getDevice(), image,
nullptr);
738 image = VK_NULL_HANDLE;
744 VkImageView createImageView(VkImage image, VkFormat format, VkImageAspectFlags aspectFlags)
const {
745 VkImageViewCreateInfo viewInfo{};
746 viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
747 viewInfo.image = image;
748 viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
749 viewInfo.format = format;
750 viewInfo.subresourceRange.aspectMask = aspectFlags;
751 viewInfo.subresourceRange.baseMipLevel = 0;
752 viewInfo.subresourceRange.levelCount = 1;
753 viewInfo.subresourceRange.baseArrayLayer = 0;
754 viewInfo.subresourceRange.layerCount = 1;
756 VkImageView imageView = VK_NULL_HANDLE;
757 if (vkCreateImageView(window->getDevice(), &viewInfo,
nullptr, &imageView) != VK_SUCCESS) {
758 throw mxvk::Exception(
"walk: failed to create raw pillar image view");
763 [[nodiscard]] VkCommandBuffer beginSingleTimeCommands()
const {
764 VkCommandBufferAllocateInfo allocInfo{};
765 allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
766 allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
767 allocInfo.commandPool = window->getCommandPool();
768 allocInfo.commandBufferCount = 1;
770 VkCommandBuffer commandBuffer = VK_NULL_HANDLE;
771 if (vkAllocateCommandBuffers(window->getDevice(), &allocInfo, &commandBuffer) != VK_SUCCESS) {
772 throw mxvk::Exception(
"walk: failed to allocate raw pillar command buffer");
775 VkCommandBufferBeginInfo beginInfo{};
776 beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
777 beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
778 if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) {
779 vkFreeCommandBuffers(window->getDevice(), window->getCommandPool(), 1, &commandBuffer);
780 throw mxvk::Exception(
"walk: failed to begin raw pillar command buffer");
783 return commandBuffer;
786 void endSingleTimeCommands(VkCommandBuffer commandBuffer)
const {
787 if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) {
788 vkFreeCommandBuffers(window->getDevice(), window->getCommandPool(), 1, &commandBuffer);
789 throw mxvk::Exception(
"walk: failed to end raw pillar command buffer");
792 VkSubmitInfo submitInfo{};
793 submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
794 submitInfo.commandBufferCount = 1;
795 submitInfo.pCommandBuffers = &commandBuffer;
797 if (vkQueueSubmit(window->getGraphicsQueue(), 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS) {
798 vkFreeCommandBuffers(window->getDevice(), window->getCommandPool(), 1, &commandBuffer);
799 throw mxvk::Exception(
"walk: failed to submit raw pillar command buffer");
801 if (vkQueueWaitIdle(window->getGraphicsQueue()) != VK_SUCCESS) {
802 vkFreeCommandBuffers(window->getDevice(), window->getCommandPool(), 1, &commandBuffer);
803 throw mxvk::Exception(
"walk: failed to wait for raw pillar upload queue");
806 vkFreeCommandBuffers(window->getDevice(), window->getCommandPool(), 1, &commandBuffer);
809 void transitionImageLayout(VkImage image, VkImageLayout oldLayout, VkImageLayout newLayout)
const {
810 VkCommandBuffer cmd = beginSingleTimeCommands();
812 VkImageMemoryBarrier barrier{};
813 barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
814 barrier.oldLayout = oldLayout;
815 barrier.newLayout = newLayout;
816 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
817 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
818 barrier.image = image;
819 barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
820 barrier.subresourceRange.baseMipLevel = 0;
821 barrier.subresourceRange.levelCount = 1;
822 barrier.subresourceRange.baseArrayLayer = 0;
823 barrier.subresourceRange.layerCount = 1;
825 VkPipelineStageFlags sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
826 VkPipelineStageFlags destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
827 if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
828 barrier.srcAccessMask = 0;
829 barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
830 }
else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) {
831 barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
832 barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
833 sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
834 destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
837 vkCmdPipelineBarrier(cmd, sourceStage, destinationStage, 0, 0,
nullptr, 0,
nullptr, 1, &barrier);
838 endSingleTimeCommands(cmd);
841 void copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height)
const {
842 VkCommandBuffer cmd = beginSingleTimeCommands();
843 VkBufferImageCopy region{};
844 region.bufferOffset = 0;
845 region.bufferRowLength = 0;
846 region.bufferImageHeight = 0;
847 region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
848 region.imageSubresource.mipLevel = 0;
849 region.imageSubresource.baseArrayLayer = 0;
850 region.imageSubresource.layerCount = 1;
851 region.imageOffset = {0, 0, 0};
852 region.imageExtent = {width, height, 1};
854 vkCmdCopyBufferToImage(cmd, buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion);
855 endSingleTimeCommands(cmd);
858 void createTextureSampler() {
859 if (textureSampler != VK_NULL_HANDLE) {
863 VkPhysicalDeviceFeatures deviceFeatures{};
864 vkGetPhysicalDeviceFeatures(window->getPhysicalDevice(), &deviceFeatures);
865 VkPhysicalDeviceProperties deviceProperties{};
866 vkGetPhysicalDeviceProperties(window->getPhysicalDevice(), &deviceProperties);
867 const bool anisotropySupported = deviceFeatures.samplerAnisotropy == VK_TRUE;
868 const float anisotropyLevel = anisotropySupported ? std::min(8.0f, deviceProperties.limits.maxSamplerAnisotropy) : 1.0f;
870 VkSamplerCreateInfo samplerInfo{};
871 samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
872 samplerInfo.magFilter = VK_FILTER_LINEAR;
873 samplerInfo.minFilter = VK_FILTER_LINEAR;
874 samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;
875 samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT;
876 samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
877 samplerInfo.anisotropyEnable = anisotropySupported ? VK_TRUE : VK_FALSE;
878 samplerInfo.maxAnisotropy = anisotropyLevel;
879 samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK;
880 samplerInfo.unnormalizedCoordinates = VK_FALSE;
881 samplerInfo.compareEnable = VK_FALSE;
882 samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS;
883 samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
885 if (vkCreateSampler(window->getDevice(), &samplerInfo,
nullptr, &textureSampler) != VK_SUCCESS) {
886 throw mxvk::Exception(
"walk: failed to create raw pillar texture sampler");
890 void createDescriptorSetLayout() {
891 if (descriptorSetLayout != VK_NULL_HANDLE) {
895 VkDescriptorSetLayoutBinding samplerBinding{};
896 samplerBinding.binding = 0;
897 samplerBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
898 samplerBinding.descriptorCount = 1;
899 samplerBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
901 VkDescriptorSetLayoutBinding uboBinding{};
902 uboBinding.binding = 1;
903 uboBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
904 uboBinding.descriptorCount = 1;
905 uboBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
907 const std::array<VkDescriptorSetLayoutBinding, 2> bindings = {samplerBinding, uboBinding};
909 VkDescriptorSetLayoutCreateInfo layoutInfo{};
910 layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
911 layoutInfo.bindingCount =
static_cast<uint32_t
>(bindings.size());
912 layoutInfo.pBindings = bindings.data();
914 if (vkCreateDescriptorSetLayout(window->getDevice(), &layoutInfo,
nullptr, &descriptorSetLayout) != VK_SUCCESS) {
915 throw mxvk::Exception(
"walk: failed to create raw pillar descriptor set layout");
919 void createUniformBuffers() {
920 destroyUniformBuffers();
922 const size_t frameCount = window->getSwapchainImageCount();
923 if (frameCount == 0) {
927 uniformBuffers.resize(frameCount, VK_NULL_HANDLE);
928 uniformBufferMemory.resize(frameCount, VK_NULL_HANDLE);
929 uniformBuffersMapped.resize(frameCount,
nullptr);
931 for (
size_t i = 0; i < frameCount; ++i) {
932 createBuffer(
sizeof(PillarUniforms), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, uniformBuffers[i], uniformBufferMemory[i]);
933 vkMapMemory(window->getDevice(), uniformBufferMemory[i], 0,
sizeof(PillarUniforms), 0, &uniformBuffersMapped[i]);
937 void createDescriptorPool() {
938 const uint32_t frameCount =
static_cast<uint32_t
>(window->getSwapchainImageCount());
939 std::array<VkDescriptorPoolSize, 2> poolSizes{};
940 poolSizes[0].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
941 poolSizes[0].descriptorCount = frameCount;
942 poolSizes[1].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
943 poolSizes[1].descriptorCount = frameCount;
945 VkDescriptorPoolCreateInfo poolInfo{};
946 poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
947 poolInfo.poolSizeCount =
static_cast<uint32_t
>(poolSizes.size());
948 poolInfo.pPoolSizes = poolSizes.data();
949 poolInfo.maxSets = frameCount;
951 if (vkCreateDescriptorPool(window->getDevice(), &poolInfo,
nullptr, &descriptorPool) != VK_SUCCESS) {
952 throw mxvk::Exception(
"walk: failed to create raw pillar descriptor pool");
956 void createDescriptorSets() {
957 const size_t frameCount = window->getSwapchainImageCount();
958 std::vector<VkDescriptorSetLayout> layouts(frameCount, descriptorSetLayout);
960 VkDescriptorSetAllocateInfo allocInfo{};
961 allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
962 allocInfo.descriptorPool = descriptorPool;
963 allocInfo.descriptorSetCount =
static_cast<uint32_t
>(frameCount);
964 allocInfo.pSetLayouts = layouts.data();
966 descriptorSets.resize(frameCount, VK_NULL_HANDLE);
967 if (vkAllocateDescriptorSets(window->getDevice(), &allocInfo, descriptorSets.data()) != VK_SUCCESS) {
968 throw mxvk::Exception(
"walk: failed to allocate raw pillar descriptor sets");
971 VkDescriptorImageInfo imageInfo{};
972 imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
973 imageInfo.imageView = textureView;
974 imageInfo.sampler = textureSampler;
976 for (
size_t i = 0; i < frameCount; ++i) {
977 VkDescriptorBufferInfo bufferInfo{};
978 bufferInfo.buffer = uniformBuffers[i];
979 bufferInfo.offset = 0;
980 bufferInfo.range =
sizeof(PillarUniforms);
982 std::array<VkWriteDescriptorSet, 2> writes{};
983 writes[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
984 writes[0].dstSet = descriptorSets[i];
985 writes[0].dstBinding = 0;
986 writes[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
987 writes[0].descriptorCount = 1;
988 writes[0].pImageInfo = &imageInfo;
990 writes[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
991 writes[1].dstSet = descriptorSets[i];
992 writes[1].dstBinding = 1;
993 writes[1].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
994 writes[1].descriptorCount = 1;
995 writes[1].pBufferInfo = &bufferInfo;
997 vkUpdateDescriptorSets(window->getDevice(),
static_cast<uint32_t
>(writes.size()), writes.data(), 0,
nullptr);
1001 void createPipeline() {
1002 if (descriptorSetLayout == VK_NULL_HANDLE || vertexSpv.empty() || fragmentSpv.empty() || window->getSwapchainFormat() == VK_FORMAT_UNDEFINED) {
1009 VkPipelineShaderStageCreateInfo vertStage{};
1010 vertStage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
1011 vertStage.stage = VK_SHADER_STAGE_VERTEX_BIT;
1012 vertStage.module = vertModule;
1013 vertStage.pName =
"main";
1015 VkPipelineShaderStageCreateInfo fragStage{};
1016 fragStage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
1017 fragStage.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
1018 fragStage.module = fragModule;
1019 fragStage.pName =
"main";
1020 const std::array<VkPipelineShaderStageCreateInfo, 2> stages = {vertStage, fragStage};
1022 VkVertexInputBindingDescription binding{};
1023 binding.binding = 0;
1024 binding.stride =
sizeof(PillarVertex);
1025 binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
1027 std::array<VkVertexInputAttributeDescription, 3> attrs{};
1028 attrs[0] = {0, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(PillarVertex, position)};
1029 attrs[1] = {1, 0, VK_FORMAT_R32G32_SFLOAT, offsetof(PillarVertex, texCoord)};
1030 attrs[2] = {2, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(PillarVertex, normal)};
1032 VkPipelineVertexInputStateCreateInfo vertexInput{};
1033 vertexInput.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
1034 vertexInput.vertexBindingDescriptionCount = 1;
1035 vertexInput.pVertexBindingDescriptions = &binding;
1036 vertexInput.vertexAttributeDescriptionCount =
static_cast<uint32_t
>(attrs.size());
1037 vertexInput.pVertexAttributeDescriptions = attrs.data();
1039 VkPipelineInputAssemblyStateCreateInfo inputAssembly{};
1040 inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
1041 inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
1043 const std::array<VkDynamicState, 2> dynamicStates = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
1044 VkPipelineDynamicStateCreateInfo dynamicInfo{};
1045 dynamicInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
1046 dynamicInfo.dynamicStateCount =
static_cast<uint32_t
>(dynamicStates.size());
1047 dynamicInfo.pDynamicStates = dynamicStates.data();
1049 VkPipelineViewportStateCreateInfo viewportState{};
1050 viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
1051 viewportState.viewportCount = 1;
1052 viewportState.scissorCount = 1;
1054 VkPipelineRasterizationStateCreateInfo rasterizer{};
1055 rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
1056 rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
1059 rasterizer.cullMode = VK_CULL_MODE_NONE;
1060 rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE;
1061 rasterizer.lineWidth = 1.0f;
1062 rasterizer.depthBiasEnable = VK_FALSE;
1064 VkPipelineMultisampleStateCreateInfo multisample{};
1065 multisample.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
1066 multisample.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
1068 VkPipelineDepthStencilStateCreateInfo depthStencil{};
1069 depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
1070 depthStencil.depthTestEnable = VK_TRUE;
1071 depthStencil.depthWriteEnable = VK_TRUE;
1072 depthStencil.depthCompareOp = VK_COMPARE_OP_LESS;
1074 VkPipelineColorBlendAttachmentState blendAttachment{};
1075 blendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
1076 blendAttachment.blendEnable = VK_FALSE;
1078 VkPipelineColorBlendStateCreateInfo colorBlend{};
1079 colorBlend.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
1080 colorBlend.attachmentCount = 1;
1081 colorBlend.pAttachments = &blendAttachment;
1083 VkPushConstantRange pushRange{};
1084 pushRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
1085 pushRange.offset = 0;
1086 pushRange.size =
sizeof(glm::mat4);
1088 VkPipelineLayoutCreateInfo layoutInfo{};
1089 layoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
1090 layoutInfo.setLayoutCount = 1;
1091 layoutInfo.pSetLayouts = &descriptorSetLayout;
1092 layoutInfo.pushConstantRangeCount = 1;
1093 layoutInfo.pPushConstantRanges = &pushRange;
1095 if (vkCreatePipelineLayout(window->getDevice(), &layoutInfo,
nullptr, &pipelineLayout) != VK_SUCCESS) {
1096 vkDestroyShaderModule(window->getDevice(), fragModule,
nullptr);
1097 vkDestroyShaderModule(window->getDevice(), vertModule,
nullptr);
1098 throw mxvk::Exception(
"walk: failed to create raw pillar pipeline layout");
1101 const VkFormat colorFormat = window->getSwapchainFormat();
1102 const VkFormat depthFormat = window->getDepthFormat();
1103 VkPipelineRenderingCreateInfo renderingInfo{};
1104 renderingInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO;
1105 renderingInfo.colorAttachmentCount = 1;
1106 renderingInfo.pColorAttachmentFormats = &colorFormat;
1107 if (depthFormat != VK_FORMAT_UNDEFINED) {
1108 renderingInfo.depthAttachmentFormat = depthFormat;
1111 VkGraphicsPipelineCreateInfo pipelineInfo{};
1112 pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
1113 pipelineInfo.pNext = &renderingInfo;
1114 pipelineInfo.stageCount =
static_cast<uint32_t
>(stages.size());
1115 pipelineInfo.pStages = stages.data();
1116 pipelineInfo.pVertexInputState = &vertexInput;
1117 pipelineInfo.pInputAssemblyState = &inputAssembly;
1118 pipelineInfo.pViewportState = &viewportState;
1119 pipelineInfo.pRasterizationState = &rasterizer;
1120 pipelineInfo.pMultisampleState = &multisample;
1121 pipelineInfo.pDepthStencilState = &depthStencil;
1122 pipelineInfo.pColorBlendState = &colorBlend;
1123 pipelineInfo.pDynamicState = &dynamicInfo;
1124 pipelineInfo.layout = pipelineLayout;
1125 pipelineInfo.renderPass = VK_NULL_HANDLE;
1127 if (vkCreateGraphicsPipelines(window->getDevice(), VK_NULL_HANDLE, 1, &pipelineInfo,
nullptr, &pipeline) != VK_SUCCESS) {
1128 vkDestroyPipelineLayout(window->getDevice(), pipelineLayout,
nullptr);
1129 pipelineLayout = VK_NULL_HANDLE;
1130 vkDestroyShaderModule(window->getDevice(), fragModule,
nullptr);
1131 vkDestroyShaderModule(window->getDevice(), vertModule,
nullptr);
1132 throw mxvk::Exception(
"walk: failed to create raw pillar graphics pipeline");
1135 vkDestroyShaderModule(window->getDevice(), fragModule,
nullptr);
1136 vkDestroyShaderModule(window->getDevice(), vertModule,
nullptr);
1139 void destroyPipeline() {
1140 if (window ==
nullptr || window->getDevice() == VK_NULL_HANDLE) {
1141 pipeline = VK_NULL_HANDLE;
1142 pipelineLayout = VK_NULL_HANDLE;
1146 if (pipeline != VK_NULL_HANDLE) {
1147 vkDestroyPipeline(window->getDevice(), pipeline,
nullptr);
1148 pipeline = VK_NULL_HANDLE;
1150 if (pipelineLayout != VK_NULL_HANDLE) {
1151 vkDestroyPipelineLayout(window->getDevice(), pipelineLayout,
nullptr);
1152 pipelineLayout = VK_NULL_HANDLE;
1156 void destroyDescriptors() {
1157 if (window ==
nullptr || window->getDevice() == VK_NULL_HANDLE) {
1158 descriptorSets.clear();
1159 descriptorPool = VK_NULL_HANDLE;
1160 descriptorSetLayout = VK_NULL_HANDLE;
1161 destroyUniformBuffers();
1165 descriptorSets.clear();
1166 if (descriptorPool != VK_NULL_HANDLE) {
1167 vkDestroyDescriptorPool(window->getDevice(), descriptorPool,
nullptr);
1168 descriptorPool = VK_NULL_HANDLE;
1170 if (descriptorSetLayout != VK_NULL_HANDLE) {
1171 vkDestroyDescriptorSetLayout(window->getDevice(), descriptorSetLayout,
nullptr);
1172 descriptorSetLayout = VK_NULL_HANDLE;
1174 destroyUniformBuffers();
1177 void destroyUniformBuffers() {
1178 if (window ==
nullptr || window->getDevice() == VK_NULL_HANDLE) {
1179 uniformBuffers.clear();
1180 uniformBufferMemory.clear();
1181 uniformBuffersMapped.clear();
1185 for (
size_t i = 0; i < uniformBuffers.size(); ++i) {
1186 if (uniformBuffersMapped[i] !=
nullptr) {
1187 vkUnmapMemory(window->getDevice(), uniformBufferMemory[i]);
1188 uniformBuffersMapped[i] =
nullptr;
1190 if (uniformBuffers[i] != VK_NULL_HANDLE) {
1191 vkDestroyBuffer(window->getDevice(), uniformBuffers[i],
nullptr);
1193 if (uniformBufferMemory[i] != VK_NULL_HANDLE) {
1194 vkFreeMemory(window->getDevice(), uniformBufferMemory[i],
nullptr);
1198 uniformBuffers.clear();
1199 uniformBufferMemory.clear();
1200 uniformBuffersMapped.clear();
1203 void destroyTexture() {
1204 if (window ==
nullptr || window->getDevice() == VK_NULL_HANDLE) {
1205 textureView = VK_NULL_HANDLE;
1206 textureImage = VK_NULL_HANDLE;
1207 textureMemory = VK_NULL_HANDLE;
1208 textureSampler = VK_NULL_HANDLE;
1212 if (textureView != VK_NULL_HANDLE) {
1213 vkDestroyImageView(window->getDevice(), textureView,
nullptr);
1214 textureView = VK_NULL_HANDLE;
1216 if (textureImage != VK_NULL_HANDLE) {
1217 vkDestroyImage(window->getDevice(), textureImage,
nullptr);
1218 textureImage = VK_NULL_HANDLE;
1220 if (textureMemory != VK_NULL_HANDLE) {
1221 vkFreeMemory(window->getDevice(), textureMemory,
nullptr);
1222 textureMemory = VK_NULL_HANDLE;
1224 if (textureSampler != VK_NULL_HANDLE) {
1225 vkDestroySampler(window->getDevice(), textureSampler,
nullptr);
1226 textureSampler = VK_NULL_HANDLE;
1230 void destroyBuffers() {
1231 if (window ==
nullptr || window->getDevice() == VK_NULL_HANDLE) {
1232 vertexBuffer = VK_NULL_HANDLE;
1233 vertexMemory = VK_NULL_HANDLE;
1234 indexBuffer = VK_NULL_HANDLE;
1235 indexMemory = VK_NULL_HANDLE;
1239 if (vertexBuffer != VK_NULL_HANDLE) {
1240 vkDestroyBuffer(window->getDevice(), vertexBuffer,
nullptr);
1241 vertexBuffer = VK_NULL_HANDLE;
1243 if (vertexMemory != VK_NULL_HANDLE) {
1244 vkFreeMemory(window->getDevice(), vertexMemory,
nullptr);
1245 vertexMemory = VK_NULL_HANDLE;
1247 if (indexBuffer != VK_NULL_HANDLE) {
1248 vkDestroyBuffer(window->getDevice(), indexBuffer,
nullptr);
1249 indexBuffer = VK_NULL_HANDLE;
1251 if (indexMemory != VK_NULL_HANDLE) {
1252 vkFreeMemory(window->getDevice(), indexMemory,
nullptr);
1253 indexMemory = VK_NULL_HANDLE;
1257 void buildGeometry() {
1258 constexpr int segments = 16;
1259 constexpr float bottomCapScale = 1.5f;
1260 constexpr float baseDepth = -0.05f;
1262 std::vector<float> vertices;
1263 std::vector<uint32_t> indices;
1264 vertices.reserve(128 * 8);
1265 indices.reserve(192);
1267 for (
int i = 0; i <= segments; ++i) {
1268 const float angle =
static_cast<float>(i) /
static_cast<float>(segments) * 2.0f * 3.14159265358979323846f;
1269 const float xBottom = std::cos(angle) * bottomCapScale;
1270 const float zBottom = std::sin(angle) * bottomCapScale;
1271 const float xTop = std::cos(angle);
1272 const float zTop = std::sin(angle);
1273 const float u =
static_cast<float>(i) /
static_cast<float>(segments);
1274 vertices.insert(vertices.end(),
1285 vertices.insert(vertices.end(),
1298 for (
int i = 0; i < segments; ++i) {
1299 const int current = i * 2;
1300 const int next = (i + 1) * 2;
1301 indices.insert(indices.end(),
1303 static_cast<uint32_t>(current),
1304 static_cast<uint32_t>(current + 1),
1305 static_cast<uint32_t>(next),
1306 static_cast<uint32_t>(next),
1307 static_cast<uint32_t>(current + 1),
1308 static_cast<uint32_t>(next + 1),
1312 const uint32_t bottomCenterIndex =
static_cast<uint32_t
>(vertices.size() / 8);
1313 vertices.insert(vertices.end(),
1325 const uint32_t bottomCapStart =
static_cast<uint32_t
>(vertices.size() / 8);
1326 for (
int i = 0; i <= segments; ++i) {
1327 const float angle =
static_cast<float>(i) /
static_cast<float>(segments) * 2.0f * 3.14159265358979323846f;
1328 const float x = std::cos(angle) * bottomCapScale;
1329 const float z = std::sin(angle) * bottomCapScale;
1330 vertices.insert(vertices.end(),
1335 0.5f + x * 0.5f / bottomCapScale,
1336 0.5f + z * 0.5f / bottomCapScale,
1342 for (
int i = 0; i < segments; ++i) {
1343 indices.insert(indices.end(),
1346 bottomCapStart + static_cast<uint32_t>(i + 1),
1347 bottomCapStart + static_cast<uint32_t>(i),
1351 const uint32_t topCenterIndex =
static_cast<uint32_t
>(vertices.size() / 8);
1352 vertices.insert(vertices.end(),
1364 const uint32_t topCapStart =
static_cast<uint32_t
>(vertices.size() / 8);
1365 for (
int i = 0; i <= segments; ++i) {
1366 const float angle =
static_cast<float>(i) /
static_cast<float>(segments) * 2.0f * 3.14159265358979323846f;
1367 const float x = std::cos(angle);
1368 const float z = std::sin(angle);
1369 vertices.insert(vertices.end(),
1381 for (
int i = 0; i < segments; ++i) {
1382 indices.insert(indices.end(),
1385 topCapStart + static_cast<uint32_t>(i),
1386 topCapStart + static_cast<uint32_t>(i + 1),
1390 vertexCount =
static_cast<uint32_t
>(vertices.size() / 8);
1391 indexCount =
static_cast<uint32_t
>(indices.size());
1393 std::vector<PillarVertex> pillarVertices(vertexCount);
1394 for (uint32_t i = 0; i < vertexCount; ++i) {
1395 const size_t base =
static_cast<size_t>(i) * 8;
1396 pillarVertices[i].position = glm::vec3(vertices[base + 0], vertices[base + 1], vertices[base + 2]);
1397 pillarVertices[i].texCoord = glm::vec2(vertices[base + 3], vertices[base + 4]);
1398 pillarVertices[i].normal = glm::vec3(vertices[base + 5], vertices[base + 6], vertices[base + 7]);
1401 createBuffer(
static_cast<VkDeviceSize
>(pillarVertices.size() *
sizeof(PillarVertex)), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, vertexBuffer, vertexMemory);
1402 void *mapped =
nullptr;
1403 vkMapMemory(window->getDevice(), vertexMemory, 0, VK_WHOLE_SIZE, 0, &mapped);
1404 std::memcpy(mapped, pillarVertices.data(), pillarVertices.size() *
sizeof(PillarVertex));
1405 vkUnmapMemory(window->getDevice(), vertexMemory);
1407 createBuffer(
static_cast<VkDeviceSize
>(indices.size() *
sizeof(uint32_t)), VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, indexBuffer, indexMemory);
1408 vkMapMemory(window->getDevice(), indexMemory, 0, VK_WHOLE_SIZE, 0, &mapped);
1409 std::memcpy(mapped, indices.data(), indices.size() *
sizeof(uint32_t));
1410 vkUnmapMemory(window->getDevice(), indexMemory);
1413 void loadTexture([[maybe_unused]]
const std::string &textureManifestPath,
const std::string &textureBasePath) {
1414 SDL_Surface *surface =
mxvk::LoadPNG((textureBasePath +
"/ground.png").c_str());
1415 if (surface ==
nullptr) {
1416 throw mxvk::Exception(
"walk: failed to load raw pillar texture");
1419 const uint32_t width =
static_cast<uint32_t
>(surface->w);
1420 const uint32_t height =
static_cast<uint32_t
>(surface->h);
1421 const VkDeviceSize imageSize =
static_cast<VkDeviceSize
>(width) *
static_cast<VkDeviceSize
>(height) * 4U;
1423 VkBuffer stagingBuffer = VK_NULL_HANDLE;
1424 VkDeviceMemory stagingMemory = VK_NULL_HANDLE;
1425 createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingMemory);
1427 void *mapped =
nullptr;
1428 vkMapMemory(window->getDevice(), stagingMemory, 0, imageSize, 0, &mapped);
1429 std::memcpy(mapped, surface->pixels,
static_cast<size_t>(imageSize));
1430 vkUnmapMemory(window->getDevice(), stagingMemory);
1432 createImage(width, height, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, textureImage, textureMemory);
1434 transitionImageLayout(textureImage, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
1435 copyBufferToImage(stagingBuffer, textureImage, width, height);
1436 transitionImageLayout(textureImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
1438 textureView = createImageView(textureImage, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_ASPECT_COLOR_BIT);
1440 vkDestroyBuffer(window->getDevice(), stagingBuffer,
nullptr);
1441 vkFreeMemory(window->getDevice(), stagingMemory,
nullptr);
1442 SDL_DestroySurface(surface);
1445 mxvk::VK_Window *window =
nullptr;
1446 std::vector<char> vertexSpv{};
1447 std::vector<char> fragmentSpv{};
1449 uint32_t vertexCount = 0;
1450 uint32_t indexCount = 0;
1451 VkBuffer vertexBuffer = VK_NULL_HANDLE;
1452 VkDeviceMemory vertexMemory = VK_NULL_HANDLE;
1453 VkBuffer indexBuffer = VK_NULL_HANDLE;
1454 VkDeviceMemory indexMemory = VK_NULL_HANDLE;
1456 VkImage textureImage = VK_NULL_HANDLE;
1457 VkDeviceMemory textureMemory = VK_NULL_HANDLE;
1458 VkImageView textureView = VK_NULL_HANDLE;
1459 VkSampler textureSampler = VK_NULL_HANDLE;
1461 VkDescriptorSetLayout descriptorSetLayout = VK_NULL_HANDLE;
1462 VkDescriptorPool descriptorPool = VK_NULL_HANDLE;
1463 std::vector<VkDescriptorSet> descriptorSets{};
1465 VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
1466 VkPipeline pipeline = VK_NULL_HANDLE;
1468 std::vector<VkBuffer> uniformBuffers{};
1469 std::vector<VkDeviceMemory> uniformBufferMemory{};
1470 std::vector<void *> uniformBuffersMapped{};
1473 class RawWallRenderer {
1476 glm::vec3 position{0.0f};
1477 glm::vec2 texCoord{0.0f};
1478 glm::vec3 normal{0.0f};
1481 struct WallUniforms {
1482 glm::mat4 view{1.0f};
1483 glm::mat4 proj{1.0f};
1487 void load(
mxvk::VK_Window *targetWindow,
const std::string &textureManifestPath,
const std::string &textureBasePath,
const std::vector<char> &vertexShaderSpv,
const std::vector<char> &fragmentShaderSpv) {
1488 if (targetWindow ==
nullptr) {
1489 throw mxvk::Exception(
"walk: raw wall renderer requires a valid window");
1491 window = targetWindow;
1492 vertSpv = vertexShaderSpv;
1493 fragSpv = fragmentShaderSpv;
1495 if (!window->ensureRenderResources()) {
1496 throw mxvk::Exception(
"walk: raw wall renderer requires render resources");
1500 loadTexture(textureManifestPath, textureBasePath);
1501 createTextureSampler();
1502 createDescriptorSetLayout();
1503 createUniformBuffers();
1504 createDescriptorPool();
1505 createDescriptorSets();
1510 if (targetWindow ==
nullptr || targetWindow->
getDevice() == VK_NULL_HANDLE) {
1514 window = targetWindow;
1516 destroyDescriptors();
1517 createDescriptorSetLayout();
1518 createUniformBuffers();
1519 createDescriptorPool();
1520 createDescriptorSets();
1527 if (window ==
nullptr || window->getDevice() == VK_NULL_HANDLE || newFragSpv.empty()) {
1530 vkDeviceWaitIdle(window->getDevice());
1531 fragSpv = newFragSpv;
1537 if (targetWindow ==
nullptr || targetWindow->
getDevice() == VK_NULL_HANDLE) {
1541 window = targetWindow;
1543 destroyDescriptors();
1549 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) {
1550 if (cmd == VK_NULL_HANDLE || pipeline == VK_NULL_HANDLE || pipelineLayout == VK_NULL_HANDLE) {
1553 if (imageIndex >= uniformBuffersMapped.size() || descriptorSets.empty() || vertexBuffer == VK_NULL_HANDLE || indexBuffer == VK_NULL_HANDLE) {
1558 uniforms.view = view;
1559 uniforms.proj = proj;
1561 std::memcpy(uniformBuffersMapped[imageIndex], &uniforms,
sizeof(
WallUniforms));
1563 vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
1564 vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets[imageIndex], 0,
nullptr);
1566 const VkDeviceSize offset = 0;
1567 vkCmdBindVertexBuffers(cmd, 0, 1, &vertexBuffer, &offset);
1568 vkCmdBindIndexBuffer(cmd, indexBuffer, 0, VK_INDEX_TYPE_UINT32);
1570 const float thickness = std::max(0.02f, wallThickness);
1573 const float wallOverlap = thickness * 0.55f;
1575 const glm::vec3 center = (segment.start + segment.end) * 0.5f;
1576 const glm::vec3 span = segment.end - segment.start;
1577 const float length = glm::length(span);
1578 if (length < 0.0001f) {
1583 constexpr float baseSink = 0.01f;
1584 glm::mat4
model = glm::translate(glm::mat4(1.0f), glm::vec3(center.x, -baseSink, center.z));
1585 model = glm::rotate(
model, std::atan2(span.z, span.x), glm::vec3(0.0f, 1.0f, 0.0f));
1586 model = glm::scale(
model, glm::vec3(length + wallOverlap * 2.0f, segment.height, thickness));
1587 vkCmdPushConstants(cmd, pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0,
sizeof(glm::mat4), &
model);
1588 vkCmdDrawIndexed(cmd, indexCount, 1, 0, 0, 0);
1593 [[nodiscard]] uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties)
const {
1594 VkPhysicalDeviceMemoryProperties memProperties{};
1595 vkGetPhysicalDeviceMemoryProperties(window->getPhysicalDevice(), &memProperties);
1596 for (uint32_t i = 0; i < memProperties.memoryTypeCount; ++i) {
1597 if ((typeFilter & (1u << i)) != 0u && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) {
1601 throw mxvk::Exception(
"walk: failed to find suitable memory type for raw wall renderer");
1604 void createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer &buffer, VkDeviceMemory &bufferMemory)
const {
1605 VkBufferCreateInfo bufferInfo{};
1606 bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
1607 bufferInfo.size = size;
1608 bufferInfo.usage = usage;
1609 bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
1611 if (vkCreateBuffer(window->getDevice(), &bufferInfo,
nullptr, &buffer) != VK_SUCCESS) {
1612 throw mxvk::Exception(
"walk: failed to create raw wall buffer");
1615 VkMemoryRequirements requirements{};
1616 vkGetBufferMemoryRequirements(window->getDevice(), buffer, &requirements);
1618 VkMemoryAllocateInfo allocInfo{};
1619 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
1620 allocInfo.allocationSize = requirements.size;
1623 allocInfo.memoryTypeIndex = findMemoryType(requirements.memoryTypeBits, properties);
1624 if (vkAllocateMemory(window->getDevice(), &allocInfo,
nullptr, &bufferMemory) != VK_SUCCESS) {
1625 throw mxvk::Exception(
"walk: failed to allocate raw wall buffer memory");
1628 if (vkBindBufferMemory(window->getDevice(), buffer, bufferMemory, 0) != VK_SUCCESS) {
1629 throw mxvk::Exception(
"walk: failed to bind raw wall buffer memory");
1632 if (bufferMemory != VK_NULL_HANDLE) {
1633 vkFreeMemory(window->getDevice(), bufferMemory,
nullptr);
1634 bufferMemory = VK_NULL_HANDLE;
1636 if (buffer != VK_NULL_HANDLE) {
1637 vkDestroyBuffer(window->getDevice(), buffer,
nullptr);
1638 buffer = VK_NULL_HANDLE;
1644 void buildGeometry() {
1647 std::vector<WallVertex> verts;
1648 std::vector<uint32_t> inds;
1652 const auto addFace = [&verts, &inds](
const glm::vec3 &v0,
const glm::vec3 &v1,
const glm::vec3 &v2,
const glm::vec3 &v3,
const glm::vec3 &normal) {
1653 const uint32_t base =
static_cast<uint32_t
>(verts.size());
1654 verts.push_back({v0, glm::vec2(0.0f, 0.0f), normal});
1655 verts.push_back({v1, glm::vec2(1.0f, 0.0f), normal});
1656 verts.push_back({v2, glm::vec2(1.0f, 1.0f), normal});
1657 verts.push_back({v3, glm::vec2(0.0f, 1.0f), normal});
1658 inds.insert(inds.end(), {base + 0, base + 1, base + 2, base + 2, base + 3, base + 0});
1661 constexpr float x = 0.5f;
1662 constexpr float z = 0.5f;
1663 constexpr float y0 = 0.0f;
1664 constexpr float y1 = 1.0f;
1666 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));
1667 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));
1668 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));
1669 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));
1670 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));
1671 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));
1673 vertexCount =
static_cast<uint32_t
>(verts.size());
1674 indexCount =
static_cast<uint32_t
>(inds.size());
1676 createBuffer(
static_cast<VkDeviceSize
>(verts.size() *
sizeof(WallVertex)), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, vertexBuffer, vertexMemory);
1677 void *mapped =
nullptr;
1678 vkMapMemory(window->getDevice(), vertexMemory, 0, VK_WHOLE_SIZE, 0, &mapped);
1679 std::memcpy(mapped, verts.data(), verts.size() *
sizeof(WallVertex));
1680 vkUnmapMemory(window->getDevice(), vertexMemory);
1682 createBuffer(
static_cast<VkDeviceSize
>(inds.size() *
sizeof(uint32_t)), VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, indexBuffer, indexMemory);
1683 vkMapMemory(window->getDevice(), indexMemory, 0, VK_WHOLE_SIZE, 0, &mapped);
1684 std::memcpy(mapped, inds.data(), inds.size() *
sizeof(uint32_t));
1685 vkUnmapMemory(window->getDevice(), indexMemory);
1688 void loadTexture([[maybe_unused]]
const std::string &textureManifestPath,
const std::string &textureBasePath) {
1689 SDL_Surface *surface =
mxvk::LoadPNG((textureBasePath +
"/wall_bricks.png").c_str());
1690 if (surface ==
nullptr) {
1691 throw mxvk::Exception(
"walk: failed to load raw wall texture");
1694 const uint32_t width =
static_cast<uint32_t
>(surface->w);
1695 const uint32_t height =
static_cast<uint32_t
>(surface->h);
1696 const VkDeviceSize imageSize =
static_cast<VkDeviceSize
>(width) *
static_cast<VkDeviceSize
>(height) * 4U;
1698 VkBuffer stagingBuffer = VK_NULL_HANDLE;
1699 VkDeviceMemory stagingMemory = VK_NULL_HANDLE;
1700 createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingMemory);
1702 void *mapped =
nullptr;
1703 vkMapMemory(window->getDevice(), stagingMemory, 0, imageSize, 0, &mapped);
1704 std::memcpy(mapped, surface->pixels,
static_cast<size_t>(imageSize));
1705 vkUnmapMemory(window->getDevice(), stagingMemory);
1707 createImage(width, height, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, textureImage, textureMemory);
1709 transitionImageLayout(textureImage, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
1710 copyBufferToImage(stagingBuffer, textureImage, width, height);
1711 transitionImageLayout(textureImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
1713 textureView = createImageView(textureImage, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_ASPECT_COLOR_BIT);
1715 vkDestroyBuffer(window->getDevice(), stagingBuffer,
nullptr);
1716 vkFreeMemory(window->getDevice(), stagingMemory,
nullptr);
1717 SDL_DestroySurface(surface);
1720 void createTextureSampler() {
1721 if (textureSampler != VK_NULL_HANDLE) {
1725 VkPhysicalDeviceFeatures deviceFeatures{};
1726 vkGetPhysicalDeviceFeatures(window->getPhysicalDevice(), &deviceFeatures);
1727 VkPhysicalDeviceProperties deviceProperties{};
1728 vkGetPhysicalDeviceProperties(window->getPhysicalDevice(), &deviceProperties);
1729 const bool anisotropySupported = deviceFeatures.samplerAnisotropy == VK_TRUE;
1730 const float anisotropyLevel = anisotropySupported ? std::min(8.0f, deviceProperties.limits.maxSamplerAnisotropy) : 1.0f;
1732 VkSamplerCreateInfo samplerInfo{};
1733 samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
1734 samplerInfo.magFilter = VK_FILTER_LINEAR;
1735 samplerInfo.minFilter = VK_FILTER_LINEAR;
1736 samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT;
1737 samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT;
1738 samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT;
1739 samplerInfo.anisotropyEnable = anisotropySupported ? VK_TRUE : VK_FALSE;
1740 samplerInfo.maxAnisotropy = anisotropyLevel;
1741 samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK;
1742 samplerInfo.unnormalizedCoordinates = VK_FALSE;
1743 samplerInfo.compareEnable = VK_FALSE;
1744 samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS;
1745 samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
1747 if (vkCreateSampler(window->getDevice(), &samplerInfo,
nullptr, &textureSampler) != VK_SUCCESS) {
1748 throw mxvk::Exception(
"walk: failed to create raw wall texture sampler");
1752 void createDescriptorSetLayout() {
1753 if (descriptorSetLayout != VK_NULL_HANDLE) {
1757 VkDescriptorSetLayoutBinding samplerBinding{};
1758 samplerBinding.binding = 0;
1759 samplerBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
1760 samplerBinding.descriptorCount = 1;
1761 samplerBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
1763 VkDescriptorSetLayoutBinding uboBinding{};
1764 uboBinding.binding = 1;
1765 uboBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
1766 uboBinding.descriptorCount = 1;
1767 uboBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
1769 const std::array<VkDescriptorSetLayoutBinding, 2> bindings = {samplerBinding, uboBinding};
1771 VkDescriptorSetLayoutCreateInfo layoutInfo{};
1772 layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
1773 layoutInfo.bindingCount =
static_cast<uint32_t
>(bindings.size());
1774 layoutInfo.pBindings = bindings.data();
1776 if (vkCreateDescriptorSetLayout(window->getDevice(), &layoutInfo,
nullptr, &descriptorSetLayout) != VK_SUCCESS) {
1777 throw mxvk::Exception(
"walk: failed to create raw wall descriptor set layout");
1781 void createUniformBuffers() {
1782 destroyUniformBuffers();
1784 const size_t frameCount = window->getSwapchainImageCount();
1785 if (frameCount == 0) {
1789 uniformBuffers.resize(frameCount, VK_NULL_HANDLE);
1790 uniformBufferMemory.resize(frameCount, VK_NULL_HANDLE);
1791 uniformBuffersMapped.resize(frameCount,
nullptr);
1793 for (
size_t i = 0; i < frameCount; ++i) {
1794 createBuffer(
sizeof(WallUniforms), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, uniformBuffers[i], uniformBufferMemory[i]);
1795 vkMapMemory(window->getDevice(), uniformBufferMemory[i], 0,
sizeof(WallUniforms), 0, &uniformBuffersMapped[i]);
1799 void createDescriptorPool() {
1800 const uint32_t frameCount =
static_cast<uint32_t
>(window->getSwapchainImageCount());
1801 std::array<VkDescriptorPoolSize, 2> poolSizes{};
1802 poolSizes[0].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
1803 poolSizes[0].descriptorCount = frameCount;
1804 poolSizes[1].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
1805 poolSizes[1].descriptorCount = frameCount;
1807 VkDescriptorPoolCreateInfo poolInfo{};
1808 poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
1809 poolInfo.poolSizeCount =
static_cast<uint32_t
>(poolSizes.size());
1810 poolInfo.pPoolSizes = poolSizes.data();
1811 poolInfo.maxSets = frameCount;
1813 if (vkCreateDescriptorPool(window->getDevice(), &poolInfo,
nullptr, &descriptorPool) != VK_SUCCESS) {
1814 throw mxvk::Exception(
"walk: failed to create raw wall descriptor pool");
1818 void createDescriptorSets() {
1819 const size_t frameCount = window->getSwapchainImageCount();
1820 std::vector<VkDescriptorSetLayout> layouts(frameCount, descriptorSetLayout);
1822 VkDescriptorSetAllocateInfo allocInfo{};
1823 allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
1824 allocInfo.descriptorPool = descriptorPool;
1825 allocInfo.descriptorSetCount =
static_cast<uint32_t
>(frameCount);
1826 allocInfo.pSetLayouts = layouts.data();
1828 descriptorSets.resize(frameCount, VK_NULL_HANDLE);
1829 if (vkAllocateDescriptorSets(window->getDevice(), &allocInfo, descriptorSets.data()) != VK_SUCCESS) {
1830 throw mxvk::Exception(
"walk: failed to allocate raw wall descriptor sets");
1833 VkDescriptorImageInfo imageInfo{};
1834 imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
1835 imageInfo.imageView = textureView;
1836 imageInfo.sampler = textureSampler;
1838 for (
size_t i = 0; i < frameCount; ++i) {
1839 VkDescriptorBufferInfo bufferInfo{};
1840 bufferInfo.buffer = uniformBuffers[i];
1841 bufferInfo.offset = 0;
1842 bufferInfo.range =
sizeof(WallUniforms);
1844 std::array<VkWriteDescriptorSet, 2> writes{};
1845 writes[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
1846 writes[0].dstSet = descriptorSets[i];
1847 writes[0].dstBinding = 0;
1848 writes[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
1849 writes[0].descriptorCount = 1;
1850 writes[0].pImageInfo = &imageInfo;
1852 writes[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
1853 writes[1].dstSet = descriptorSets[i];
1854 writes[1].dstBinding = 1;
1855 writes[1].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
1856 writes[1].descriptorCount = 1;
1857 writes[1].pBufferInfo = &bufferInfo;
1859 vkUpdateDescriptorSets(window->getDevice(),
static_cast<uint32_t
>(writes.size()), writes.data(), 0,
nullptr);
1863 void createPipeline() {
1864 if (descriptorSetLayout == VK_NULL_HANDLE || vertSpv.empty() || fragSpv.empty() || window->getSwapchainFormat() == VK_FORMAT_UNDEFINED) {
1871 VkPipelineShaderStageCreateInfo vertStage{};
1872 vertStage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
1873 vertStage.stage = VK_SHADER_STAGE_VERTEX_BIT;
1874 vertStage.module = vertModule;
1875 vertStage.pName =
"main";
1877 VkPipelineShaderStageCreateInfo fragStage{};
1878 fragStage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
1879 fragStage.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
1880 fragStage.module = fragModule;
1881 fragStage.pName =
"main";
1882 const std::array<VkPipelineShaderStageCreateInfo, 2> stages = {vertStage, fragStage};
1884 VkVertexInputBindingDescription binding{};
1885 binding.binding = 0;
1886 binding.stride =
sizeof(WallVertex);
1887 binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
1889 std::array<VkVertexInputAttributeDescription, 3> attrs{};
1890 attrs[0] = {0, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(WallVertex, position)};
1891 attrs[1] = {1, 0, VK_FORMAT_R32G32_SFLOAT, offsetof(WallVertex, texCoord)};
1892 attrs[2] = {2, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(WallVertex, normal)};
1894 VkPipelineVertexInputStateCreateInfo vertexInput{};
1895 vertexInput.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
1896 vertexInput.vertexBindingDescriptionCount = 1;
1897 vertexInput.pVertexBindingDescriptions = &binding;
1898 vertexInput.vertexAttributeDescriptionCount =
static_cast<uint32_t
>(attrs.size());
1899 vertexInput.pVertexAttributeDescriptions = attrs.data();
1901 VkPipelineInputAssemblyStateCreateInfo inputAssembly{};
1902 inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
1903 inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
1905 const std::array<VkDynamicState, 2> dynamicStates = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
1906 VkPipelineDynamicStateCreateInfo dynamicInfo{};
1907 dynamicInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
1908 dynamicInfo.dynamicStateCount =
static_cast<uint32_t
>(dynamicStates.size());
1909 dynamicInfo.pDynamicStates = dynamicStates.data();
1911 VkPipelineViewportStateCreateInfo viewportState{};
1912 viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
1913 viewportState.viewportCount = 1;
1914 viewportState.scissorCount = 1;
1916 VkPipelineRasterizationStateCreateInfo rasterizer{};
1917 rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
1918 rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
1919 rasterizer.cullMode = VK_CULL_MODE_NONE;
1920 rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE;
1921 rasterizer.lineWidth = 1.0f;
1923 VkPipelineMultisampleStateCreateInfo multisample{};
1924 multisample.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
1925 multisample.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
1927 VkPipelineDepthStencilStateCreateInfo depthStencil{};
1928 depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
1929 depthStencil.depthTestEnable = VK_TRUE;
1930 depthStencil.depthWriteEnable = VK_TRUE;
1931 depthStencil.depthCompareOp = VK_COMPARE_OP_LESS;
1933 VkPipelineColorBlendAttachmentState blendAttachment{};
1934 blendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
1935 blendAttachment.blendEnable = VK_FALSE;
1937 VkPipelineColorBlendStateCreateInfo colorBlend{};
1938 colorBlend.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
1939 colorBlend.attachmentCount = 1;
1940 colorBlend.pAttachments = &blendAttachment;
1942 VkPushConstantRange pushRange{};
1943 pushRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
1944 pushRange.offset = 0;
1945 pushRange.size =
sizeof(glm::mat4);
1947 VkPipelineLayoutCreateInfo layoutInfo{};
1948 layoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
1949 layoutInfo.setLayoutCount = 1;
1950 layoutInfo.pSetLayouts = &descriptorSetLayout;
1951 layoutInfo.pushConstantRangeCount = 1;
1952 layoutInfo.pPushConstantRanges = &pushRange;
1954 if (vkCreatePipelineLayout(window->getDevice(), &layoutInfo,
nullptr, &pipelineLayout) != VK_SUCCESS) {
1955 vkDestroyShaderModule(window->getDevice(), fragModule,
nullptr);
1956 vkDestroyShaderModule(window->getDevice(), vertModule,
nullptr);
1957 throw mxvk::Exception(
"walk: failed to create raw wall pipeline layout");
1960 const VkFormat colorFormat = window->getSwapchainFormat();
1961 const VkFormat depthFormat = window->getDepthFormat();
1962 VkPipelineRenderingCreateInfo renderingInfo{};
1963 renderingInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO;
1964 renderingInfo.colorAttachmentCount = 1;
1965 renderingInfo.pColorAttachmentFormats = &colorFormat;
1966 if (depthFormat != VK_FORMAT_UNDEFINED) {
1967 renderingInfo.depthAttachmentFormat = depthFormat;
1970 VkGraphicsPipelineCreateInfo pipelineInfo{};
1971 pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
1972 pipelineInfo.pNext = &renderingInfo;
1973 pipelineInfo.stageCount =
static_cast<uint32_t
>(stages.size());
1974 pipelineInfo.pStages = stages.data();
1975 pipelineInfo.pVertexInputState = &vertexInput;
1976 pipelineInfo.pInputAssemblyState = &inputAssembly;
1977 pipelineInfo.pViewportState = &viewportState;
1978 pipelineInfo.pRasterizationState = &rasterizer;
1979 pipelineInfo.pMultisampleState = &multisample;
1980 pipelineInfo.pDepthStencilState = &depthStencil;
1981 pipelineInfo.pColorBlendState = &colorBlend;
1982 pipelineInfo.pDynamicState = &dynamicInfo;
1983 pipelineInfo.layout = pipelineLayout;
1984 pipelineInfo.renderPass = VK_NULL_HANDLE;
1986 if (vkCreateGraphicsPipelines(window->getDevice(), VK_NULL_HANDLE, 1, &pipelineInfo,
nullptr, &pipeline) != VK_SUCCESS) {
1987 vkDestroyPipelineLayout(window->getDevice(), pipelineLayout,
nullptr);
1988 pipelineLayout = VK_NULL_HANDLE;
1989 vkDestroyShaderModule(window->getDevice(), fragModule,
nullptr);
1990 vkDestroyShaderModule(window->getDevice(), vertModule,
nullptr);
1991 throw mxvk::Exception(
"walk: failed to create raw wall graphics pipeline");
1994 vkDestroyShaderModule(window->getDevice(), fragModule,
nullptr);
1995 vkDestroyShaderModule(window->getDevice(), vertModule,
nullptr);
1998 void destroyPipeline() {
1999 if (window ==
nullptr || window->getDevice() == VK_NULL_HANDLE) {
2000 pipeline = VK_NULL_HANDLE;
2001 pipelineLayout = VK_NULL_HANDLE;
2005 if (pipeline != VK_NULL_HANDLE) {
2006 vkDestroyPipeline(window->getDevice(), pipeline,
nullptr);
2007 pipeline = VK_NULL_HANDLE;
2009 if (pipelineLayout != VK_NULL_HANDLE) {
2010 vkDestroyPipelineLayout(window->getDevice(), pipelineLayout,
nullptr);
2011 pipelineLayout = VK_NULL_HANDLE;
2015 void destroyDescriptors() {
2016 if (window ==
nullptr || window->getDevice() == VK_NULL_HANDLE) {
2017 descriptorSets.clear();
2018 descriptorPool = VK_NULL_HANDLE;
2019 descriptorSetLayout = VK_NULL_HANDLE;
2020 destroyUniformBuffers();
2024 descriptorSets.clear();
2025 if (descriptorPool != VK_NULL_HANDLE) {
2026 vkDestroyDescriptorPool(window->getDevice(), descriptorPool,
nullptr);
2027 descriptorPool = VK_NULL_HANDLE;
2029 if (descriptorSetLayout != VK_NULL_HANDLE) {
2030 vkDestroyDescriptorSetLayout(window->getDevice(), descriptorSetLayout,
nullptr);
2031 descriptorSetLayout = VK_NULL_HANDLE;
2033 destroyUniformBuffers();
2036 void destroyUniformBuffers() {
2037 if (window ==
nullptr || window->getDevice() == VK_NULL_HANDLE) {
2038 uniformBuffers.clear();
2039 uniformBufferMemory.clear();
2040 uniformBuffersMapped.clear();
2044 for (
size_t i = 0; i < uniformBuffers.size(); ++i) {
2045 if (uniformBuffersMapped[i] !=
nullptr) {
2046 vkUnmapMemory(window->getDevice(), uniformBufferMemory[i]);
2047 uniformBuffersMapped[i] =
nullptr;
2049 if (uniformBuffers[i] != VK_NULL_HANDLE) {
2050 vkDestroyBuffer(window->getDevice(), uniformBuffers[i],
nullptr);
2052 if (uniformBufferMemory[i] != VK_NULL_HANDLE) {
2053 vkFreeMemory(window->getDevice(), uniformBufferMemory[i],
nullptr);
2057 uniformBuffers.clear();
2058 uniformBufferMemory.clear();
2059 uniformBuffersMapped.clear();
2062 void destroyTexture() {
2063 if (window ==
nullptr || window->getDevice() == VK_NULL_HANDLE) {
2064 textureView = VK_NULL_HANDLE;
2065 textureImage = VK_NULL_HANDLE;
2066 textureMemory = VK_NULL_HANDLE;
2067 textureSampler = VK_NULL_HANDLE;
2071 if (textureView != VK_NULL_HANDLE) {
2072 vkDestroyImageView(window->getDevice(), textureView,
nullptr);
2073 textureView = VK_NULL_HANDLE;
2075 if (textureImage != VK_NULL_HANDLE) {
2076 vkDestroyImage(window->getDevice(), textureImage,
nullptr);
2077 textureImage = VK_NULL_HANDLE;
2079 if (textureMemory != VK_NULL_HANDLE) {
2080 vkFreeMemory(window->getDevice(), textureMemory,
nullptr);
2081 textureMemory = VK_NULL_HANDLE;
2083 if (textureSampler != VK_NULL_HANDLE) {
2084 vkDestroySampler(window->getDevice(), textureSampler,
nullptr);
2085 textureSampler = VK_NULL_HANDLE;
2089 void destroyBuffers() {
2090 if (window ==
nullptr || window->getDevice() == VK_NULL_HANDLE) {
2091 vertexBuffer = VK_NULL_HANDLE;
2092 vertexMemory = VK_NULL_HANDLE;
2093 indexBuffer = VK_NULL_HANDLE;
2094 indexMemory = VK_NULL_HANDLE;
2098 if (vertexBuffer != VK_NULL_HANDLE) {
2099 vkDestroyBuffer(window->getDevice(), vertexBuffer,
nullptr);
2100 vertexBuffer = VK_NULL_HANDLE;
2102 if (vertexMemory != VK_NULL_HANDLE) {
2103 vkFreeMemory(window->getDevice(), vertexMemory,
nullptr);
2104 vertexMemory = VK_NULL_HANDLE;
2106 if (indexBuffer != VK_NULL_HANDLE) {
2107 vkDestroyBuffer(window->getDevice(), indexBuffer,
nullptr);
2108 indexBuffer = VK_NULL_HANDLE;
2110 if (indexMemory != VK_NULL_HANDLE) {
2111 vkFreeMemory(window->getDevice(), indexMemory,
nullptr);
2112 indexMemory = VK_NULL_HANDLE;
2116 void createImage(uint32_t width, uint32_t height, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, VkImage &image, VkDeviceMemory &memory)
const {
2117 VkImageCreateInfo imageInfo{};
2118 imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
2119 imageInfo.imageType = VK_IMAGE_TYPE_2D;
2120 imageInfo.extent.width = width;
2121 imageInfo.extent.height = height;
2122 imageInfo.extent.depth = 1;
2123 imageInfo.mipLevels = 1;
2124 imageInfo.arrayLayers = 1;
2125 imageInfo.format = format;
2126 imageInfo.tiling = tiling;
2127 imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
2128 imageInfo.usage = usage;
2129 imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
2130 imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
2132 if (vkCreateImage(window->getDevice(), &imageInfo,
nullptr, &image) != VK_SUCCESS) {
2133 throw mxvk::Exception(
"walk: failed to create raw wall image");
2136 VkMemoryRequirements requirements{};
2137 vkGetImageMemoryRequirements(window->getDevice(), image, &requirements);
2139 VkMemoryAllocateInfo allocInfo{};
2140 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
2141 allocInfo.allocationSize = requirements.size;
2144 allocInfo.memoryTypeIndex = findMemoryType(requirements.memoryTypeBits, properties);
2145 if (vkAllocateMemory(window->getDevice(), &allocInfo,
nullptr, &memory) != VK_SUCCESS) {
2146 throw mxvk::Exception(
"walk: failed to allocate raw wall image memory");
2149 if (vkBindImageMemory(window->getDevice(), image, memory, 0) != VK_SUCCESS) {
2150 throw mxvk::Exception(
"walk: failed to bind raw wall image memory");
2153 if (memory != VK_NULL_HANDLE) {
2154 vkFreeMemory(window->getDevice(), memory,
nullptr);
2155 memory = VK_NULL_HANDLE;
2157 if (image != VK_NULL_HANDLE) {
2158 vkDestroyImage(window->getDevice(), image,
nullptr);
2159 image = VK_NULL_HANDLE;
2165 VkImageView createImageView(VkImage image, VkFormat format, VkImageAspectFlags aspectFlags)
const {
2166 VkImageViewCreateInfo viewInfo{};
2167 viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
2168 viewInfo.image = image;
2169 viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
2170 viewInfo.format = format;
2171 viewInfo.subresourceRange.aspectMask = aspectFlags;
2172 viewInfo.subresourceRange.baseMipLevel = 0;
2173 viewInfo.subresourceRange.levelCount = 1;
2174 viewInfo.subresourceRange.baseArrayLayer = 0;
2175 viewInfo.subresourceRange.layerCount = 1;
2177 VkImageView imageView = VK_NULL_HANDLE;
2178 if (vkCreateImageView(window->getDevice(), &viewInfo,
nullptr, &imageView) != VK_SUCCESS) {
2179 throw mxvk::Exception(
"walk: failed to create raw wall image view");
2184 [[nodiscard]] VkCommandBuffer beginSingleTimeCommands()
const {
2185 VkCommandBufferAllocateInfo allocInfo{};
2186 allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
2187 allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
2188 allocInfo.commandPool = window->getCommandPool();
2189 allocInfo.commandBufferCount = 1;
2191 VkCommandBuffer commandBuffer = VK_NULL_HANDLE;
2192 if (vkAllocateCommandBuffers(window->getDevice(), &allocInfo, &commandBuffer) != VK_SUCCESS) {
2193 throw mxvk::Exception(
"walk: failed to allocate raw wall command buffer");
2196 VkCommandBufferBeginInfo beginInfo{};
2197 beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
2198 beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
2199 if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) {
2200 vkFreeCommandBuffers(window->getDevice(), window->getCommandPool(), 1, &commandBuffer);
2201 throw mxvk::Exception(
"walk: failed to begin raw wall command buffer");
2204 return commandBuffer;
2207 void endSingleTimeCommands(VkCommandBuffer commandBuffer)
const {
2208 if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) {
2209 vkFreeCommandBuffers(window->getDevice(), window->getCommandPool(), 1, &commandBuffer);
2210 throw mxvk::Exception(
"walk: failed to end raw wall command buffer");
2213 VkSubmitInfo submitInfo{};
2214 submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
2215 submitInfo.commandBufferCount = 1;
2216 submitInfo.pCommandBuffers = &commandBuffer;
2218 if (vkQueueSubmit(window->getGraphicsQueue(), 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS) {
2219 vkFreeCommandBuffers(window->getDevice(), window->getCommandPool(), 1, &commandBuffer);
2220 throw mxvk::Exception(
"walk: failed to submit raw wall command buffer");
2222 if (vkQueueWaitIdle(window->getGraphicsQueue()) != VK_SUCCESS) {
2223 vkFreeCommandBuffers(window->getDevice(), window->getCommandPool(), 1, &commandBuffer);
2224 throw mxvk::Exception(
"walk: failed to wait for raw wall upload queue");
2227 vkFreeCommandBuffers(window->getDevice(), window->getCommandPool(), 1, &commandBuffer);
2230 void transitionImageLayout(VkImage image, VkImageLayout oldLayout, VkImageLayout newLayout)
const {
2231 VkCommandBuffer cmd = beginSingleTimeCommands();
2233 VkImageMemoryBarrier barrier{};
2234 barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
2235 barrier.oldLayout = oldLayout;
2236 barrier.newLayout = newLayout;
2237 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
2238 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
2239 barrier.image = image;
2240 barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
2241 barrier.subresourceRange.baseMipLevel = 0;
2242 barrier.subresourceRange.levelCount = 1;
2243 barrier.subresourceRange.baseArrayLayer = 0;
2244 barrier.subresourceRange.layerCount = 1;
2246 VkPipelineStageFlags sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
2247 VkPipelineStageFlags destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
2248 if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
2249 barrier.srcAccessMask = 0;
2250 barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
2251 }
else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) {
2252 barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
2253 barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
2254 sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
2255 destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
2258 vkCmdPipelineBarrier(cmd, sourceStage, destinationStage, 0, 0,
nullptr, 0,
nullptr, 1, &barrier);
2259 endSingleTimeCommands(cmd);
2262 void copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height)
const {
2263 VkCommandBuffer cmd = beginSingleTimeCommands();
2264 VkBufferImageCopy region{};
2265 region.bufferOffset = 0;
2266 region.bufferRowLength = 0;
2267 region.bufferImageHeight = 0;
2268 region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
2269 region.imageSubresource.mipLevel = 0;
2270 region.imageSubresource.baseArrayLayer = 0;
2271 region.imageSubresource.layerCount = 1;
2272 region.imageOffset = {0, 0, 0};
2273 region.imageExtent = {width, height, 1};
2275 vkCmdCopyBufferToImage(cmd, buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion);
2276 endSingleTimeCommands(cmd);
2279 std::vector<char> vertSpv{};
2280 std::vector<char> fragSpv{};
2282 uint32_t vertexCount = 0;
2283 uint32_t indexCount = 0;
2284 VkBuffer vertexBuffer = VK_NULL_HANDLE;
2285 VkDeviceMemory vertexMemory = VK_NULL_HANDLE;
2286 VkBuffer indexBuffer = VK_NULL_HANDLE;
2287 VkDeviceMemory indexMemory = VK_NULL_HANDLE;
2289 VkImage textureImage = VK_NULL_HANDLE;
2290 VkDeviceMemory textureMemory = VK_NULL_HANDLE;
2291 VkImageView textureView = VK_NULL_HANDLE;
2292 VkSampler textureSampler = VK_NULL_HANDLE;
2294 VkDescriptorSetLayout descriptorSetLayout = VK_NULL_HANDLE;
2295 VkDescriptorPool descriptorPool = VK_NULL_HANDLE;
2296 std::vector<VkDescriptorSet> descriptorSets{};
2298 VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
2299 VkPipeline pipeline = VK_NULL_HANDLE;
2301 std::vector<VkBuffer> uniformBuffers{};
2302 std::vector<VkDeviceMemory> uniformBufferMemory{};
2303 std::vector<void *> uniformBuffersMapped{};
2305 VkDevice device [[maybe_unused]] = VK_NULL_HANDLE;
2306 mxvk::VK_Window *window =
nullptr;
2311 WalkWindow(
const Arguments &args) :
mxvk::
VK_IOWindow(args.path,
"FPS Maze Room - MXVK", args.width, args.height, args.fullscreen, args.enable_vsync), assetRoot((args.path.empty() || args.path ==
".") ? std::string(WALK_ASSET_DIR) : args.path), shaderRoot(assetRoot +
"/data"), modelRoot(assetRoot +
"/data") {
2312 logEnv(std::format(
"initializing window {}x{} (fullscreen={})", args.
width, args.
height, args.
fullscreen ?
"true" :
"false"));
2313 logEnv(std::format(
"asset root: {}", assetRoot));
2314 logEnv(std::format(
"model root: {}", modelRoot));
2316 std::mt19937 rng(
static_cast<uint32_t
>(std::chrono::high_resolution_clock::now().time_since_epoch().count()));
2317 world.generate(rng());
2318 logEnv(std::format(
"world generated (walls={}, pillars={}, collectibles={})", world.walls().size(), world.pillars().size(), world.collectibles().size()));
2319 setClearColor(100.0f / 255.0f, 181.0f / 255.0f, 246.0f / 255.0f, 1.0f);
2321 cameraPos = world.startPosition();
2322 yaw = chooseBestSpawnYaw(cameraPos);
2324 updateCameraVectors();
2326 setFont(assetRoot +
"/data/font.ttf", 22);
2328 const std::string vertPath = shaderRoot +
"/model.vert.spv";
2329 const std::string wallFragPath = shaderRoot +
"/wall.frag.spv";
2330 const std::string floorFragPath = shaderRoot +
"/floor.frag.spv";
2331 const std::string pillarVertPath = shaderRoot +
"/pillar.vert.spv";
2332 const std::string pillarFragPath = shaderRoot +
"/pillar.frag.spv";
2333 const std::string objectFragPath = shaderRoot +
"/object.frag.spv";
2334 const std::string bulletFragPath = shaderRoot +
"/bullet.frag.spv";
2335 const std::string particleFragPath = shaderRoot +
"/particle.frag.spv";
2336 const std::string groundTexManifest = assetRoot +
"/data/ground.tex";
2338 modelVertSpv = vertPath;
2339 pillarVertSpv = pillarVertPath;
2340 wallFragSpv = wallFragPath;
2341 floorFragSpv = floorFragPath;
2342 pillarFragSpv = pillarFragPath;
2343 objectFragSpv = objectFragPath;
2344 bulletFragSpv = bulletFragPath;
2346 loadModel(floorModel, modelRoot +
"/cube.mxmod.z", groundTexManifest, assetRoot +
"/data", vertPath, floorFragPath);
2347 loadModel(bulletModel, modelRoot +
"/sphere.mxmod.z",
"",
"", vertPath, bulletFragPath);
2349 logEnv(
"loading wall renderer assets");
2350 rawWallRenderer.load(
this, groundTexManifest, assetRoot +
"/data",
loadSpv(pillarVertPath),
loadSpv(wallFragPath));
2351 logEnv(
"wall renderer ready");
2353 logEnv(
"loading pillar renderer assets");
2354 rawPillarRenderer.load(
this, groundTexManifest, assetRoot +
"/data",
loadSpv(pillarVertPath),
loadSpv(pillarFragPath));
2355 logEnv(
"pillar renderer ready");
2357 loadModel(saturnModel, assetRoot +
"/data/saturn.mxmod.z", assetRoot +
"/data/planet.tex", assetRoot +
"/data", vertPath, objectFragPath);
2358 loadModel(birdModel, assetRoot +
"/data/tux.obj", assetRoot +
"/data/tux.mtl", assetRoot +
"/data", vertPath, objectFragPath);
2359 loadModel(blasterModel, assetRoot +
"/data/blaster.obj", assetRoot +
"/data/blaster.mtl", assetRoot +
"/data", vertPath, objectFragPath);
2360 normalizeCollectiblesToModel();
2362 pointParticleVertSpv = shaderRoot +
"/particle_points.vert.spv";
2363 pointParticleFragSpv = shaderRoot +
"/particle_points.frag.spv";
2364 initializePointParticles();
2365 logEnv(
"point-particle pipeline initialized");
2367 loadPostProcessingShaderIndex(args.
shaderPath);
2369 applyPostProcessingShaderSelection();
2371 tryOpenFirstGamepad();
2373 logEnv(
"mouse capture enabled");
2377 logEnv(
"shutting down walk window");
2378 if (gamepad !=
nullptr) {
2379 SDL_CloseGamepad(gamepad);
2383 if (
device != VK_NULL_HANDLE) {
2384 vkDeviceWaitIdle(
device);
2385 destroyPointParticles();
2391 const bool is_left_double_click = (e.type == SDL_EVENT_MOUSE_BUTTON_DOWN && e.button.button == SDL_BUTTON_LEFT && e.button.clicks >= 2);
2393 if (is_left_double_click) {
2394 if (SDL_Window *
const sdlWindow =
getSDLWindow(); sdlWindow !=
nullptr) {
2395 SDL_RaiseWindow(sdlWindow);
2396 SDL_SetWindowMouseGrab(sdlWindow,
true);
2397 SDL_SetWindowRelativeMouseMode(sdlWindow,
true);
2400 mouseCapture =
true;
2403 suppressProjectileOnNextLeftDown =
true;
2405 logEnv(
"mouse capture enabled (double-click)");
2412 if (e.type == SDL_EVENT_QUIT) {
2413 logEnv(
"received quit event");
2418 if (e.type == SDL_EVENT_KEY_DOWN) {
2419 if (e.key.key == SDLK_ESCAPE) {
2421 mouseCapture =
false;
2423 suppressProjectileOnNextLeftDown =
false;
2424 logEnv(
"mouse capture disabled (ESC)");
2426 logEnv(
"exit requested by ESC");
2430 }
else if (e.key.key == SDLK_F) {
2432 logEnv(std::format(
"FPS overlay {}", showFps ?
"enabled" :
"disabled"));
2433 }
else if (e.key.key == SDLK_R && !e.key.repeat) {
2434 selectPostProcessingShader(-1);
2435 }
else if (e.key.key == SDLK_T && !e.key.repeat) {
2436 selectPostProcessingShader(1);
2440 if (e.type == SDL_EVENT_GAMEPAD_ADDED) {
2441 logEnv(std::format(
"gamepad added (id={})",
static_cast<int>(e.gdevice.which)));
2442 openGamepad(e.gdevice.which);
2445 if (e.type == SDL_EVENT_GAMEPAD_REMOVED) {
2446 if (gamepad !=
nullptr && e.gdevice.which == gamepadId) {
2447 logEnv(std::format(
"gamepad removed (id={})",
static_cast<int>(e.gdevice.which)));
2448 SDL_CloseGamepad(gamepad);
2454 if (e.type == SDL_EVENT_GAMEPAD_BUTTON_DOWN) {
2455 if (e.gbutton.button == SDL_GAMEPAD_BUTTON_BACK || e.gbutton.button == SDL_GAMEPAD_BUTTON_START) {
2456 logEnv(
"exit requested by gamepad back/start");
2458 }
else if (e.gbutton.button == SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER) {
2460 }
else if (e.gbutton.button == SDL_GAMEPAD_BUTTON_SOUTH && cameraPos.y <= 1.71f) {
2461 jumpVelocity = 0.3f;
2462 logEnv(
"jump triggered by gamepad");
2466 if (e.type == SDL_EVENT_MOUSE_MOTION && mouseCapture) {
2471 yaw +=
static_cast<float>(e.motion.xrel) * mouseSensitivity;
2472 pitch -=
static_cast<float>(e.motion.yrel) * mouseSensitivity;
2473 pitch = glm::clamp(pitch, -89.0f, 89.0f);
2474 updateCameraVectors();
2477 if (e.type == SDL_EVENT_MOUSE_BUTTON_DOWN && e.button.button == SDL_BUTTON_LEFT && mouseCapture) {
2478 if (suppressProjectileOnNextLeftDown) {
2479 suppressProjectileOnNextLeftDown =
false;
2487 tryOpenFirstGamepad();
2488 const auto now = std::chrono::steady_clock::now();
2489 float deltaTime = std::chrono::duration<float>(now - lastTick).count();
2491 deltaTime = std::clamp(deltaTime, 0.0f, 0.05f);
2492 updatePostProcessingShaderUniforms(deltaTime);
2495 updatePlayer(deltaTime);
2497 updateProjectiles(deltaTime);
2498 updateExplosions(deltaTime);
2499 updateCollectibles(deltaTime);
2501 const int aliveObjects = world.activeCollectibles();
2503 printText(std::format(
"Objects left: {}", aliveObjects), 20, 20, {255, 255, 255, 255});
2504 printText(std::format(
"Active Bullets: {}", bullets.size()), 20, 48, {255, 220, 120, 255});
2505 if (showFps && deltaTime > 0.0001f) {
2506 const int fps =
static_cast<int>(1.0f / deltaTime);
2507 printText(std::format(
"FPS: {}", fps), 20, 76, {120, 255, 120, 255});
2511 const int cx =
static_cast<int>(extent.width / 2U);
2512 const int cy =
static_cast<int>(extent.height / 2U);
2513 printText(
"+", cx - 6, cy - 12, {255, 64, 64, 255});
2514 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});
2515 if (!postProcessingShaders.empty()) {
2516 printText(std::format(
"Post FX: {} / {} R/T", postProcessingShaderIndex + 1, postProcessingShaders.size()), 20, 104, {180, 220, 255, 255});
2522 logEnv(
"swapchain recreated; resizing render resources");
2523 floorModel.resize(
this);
2524 rawWallRenderer.resize(
this);
2525 rawPillarRenderer.resize(
this);
2526 saturnModel.resize(
this);
2527 birdModel.resize(
this);
2528 blasterModel.resize(
this);
2529 bulletModel.resize(
this);
2530 rebuildPointParticlePipeline();
2535 const float aspect = (extent.height > 0U) ?
static_cast<float>(extent.width) /
static_cast<float>(extent.height) : 1.0f;
2537 const glm::mat4 view = glm::lookAt(cameraPos, cameraPos + cameraFront, glm::vec3(0.0f, 1.0f, 0.0f));
2538 glm::mat4 proj = glm::perspective(glm::radians(45.0f), aspect, 0.1f, 1000.0f);
2539 proj[1][1] *= -1.0f;
2541 const float t =
static_cast<float>(SDL_GetTicks()) * 0.001f;
2545 constexpr float floorHalfSize = 100.0f;
2546 constexpr float floorThickness = 0.04f;
2547 const glm::vec3 extent = floorModel.modelAxisExtent();
2548 const glm::vec3 srcScale((floorHalfSize * 2.0f) / std::max(extent.x, 1e-4f), floorThickness / std::max(extent.y, 1e-4f), (floorHalfSize * 2.0f) / std::max(extent.z, 1e-4f));
2549 glm::mat4 floorWorld = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, -0.02f, 0.0f));
2550 floorWorld = glm::scale(floorWorld, srcScale);
2551 renderModel(cmd, imageIndex, floorModel, floorWorld, view, proj, glm::vec4(0.0f, 0.0f, 0.0f, t),
false);
2554 rawWallRenderer.render(cmd, imageIndex, world.walls(), world.wallThickness(), view, proj, glm::vec4(0.58f, 0.58f, 0.65f, t));
2556 rawPillarRenderer.render(cmd, imageIndex, world.pillars(), view, proj, glm::vec4(0.0f, 0.0f, 0.0f, t));
2558 for (
const Collectible &obj : world.collectibles()) {
2562 glm::mat4 world = glm::translate(glm::mat4(1.0f), obj.position);
2563 world = glm::rotate(world, glm::radians(obj.rotation.y), glm::vec3(0.0f, 1.0f, 0.0f));
2564 world = glm::scale(world, obj.scale);
2566 renderRawModel(cmd, imageIndex, saturnModel, world, view, proj, glm::vec4(cameraPos, 0.0f));
2568 renderRawModel(cmd, imageIndex, birdModel, world, view, proj, glm::vec4(cameraPos, 0.0f));
2573 renderRawModel(cmd, imageIndex, blasterModel, blasterWorldTransform(), view, proj, glm::vec4(cameraPos, 0.0f));
2577 if (!bullet.active) {
2580 if (bullet.lifetime < 0.05f) {
2583 glm::mat4 world = glm::translate(glm::mat4(1.0f), bullet.position);
2584 world = glm::scale(world, glm::vec3(0.07f, 0.07f, 0.20f));
2585 const float fadeProgress = glm::clamp(bullet.lifetime / bullet.maxLifetime, 0.0f, 1.0f);
2586 const float distanceProgress = glm::clamp(bullet.distanceTraveled / bullet.maxDistance, 0.0f, 1.0f);
2587 const float alpha = std::min(1.0f - fadeProgress, 1.0f - distanceProgress);
2588 renderRawModel(cmd, imageIndex, bulletModel, world, view, proj, glm::vec4(alpha, 0.0f, 0.0f, 0.0f));
2590 renderPointParticles(cmd, view, proj);
2594 enum class ProjectileHitType {
2601 struct ProjectileTraceHit {
2602 ProjectileHitType type = ProjectileHitType::None;
2603 glm::vec3 impact{0.0f};
2604 size_t collectibleIndex = 0;
2607 [[nodiscard]] ProjectileTraceHit traceProjectileSegment(
const glm::vec3 &from,
const glm::vec3 &to)
const {
2608 const glm::vec3 dir = to - from;
2609 const float travel = glm::length(dir);
2610 if (travel <= 1e-8f) {
2614 constexpr float sampleStride = 0.03f;
2615 constexpr float projectileRadius = 0.015f;
2616 const int steps = std::max(1,
static_cast<int>(std::ceil(travel / sampleStride)));
2617 for (
int i = 0; i <= steps; ++i) {
2618 const float t =
static_cast<float>(i) /
static_cast<float>(steps);
2619 const glm::vec3 point = from + (dir * t);
2620 if (pointHitsWall3D(point, projectileRadius)) {
2621 return {ProjectileHitType::Wall, point, 0};
2623 if (pointHitsPillar3D(point, projectileRadius)) {
2624 return {ProjectileHitType::Pillar, point, 0};
2626 if (point.y <= 0.0f) {
2627 return {ProjectileHitType::Floor, point, 0};
2634 bool handleConsoleCommand(
const std::vector<std::string> &args, std::ostream &out)
override {
2639 const std::string &cmd = args[0];
2641 if (cmd ==
"spawn_random" || (cmd ==
"spawn" && args.size() >= 2 && args[1] ==
"random")) {
2642 const int attempts = (args.size() >= 3 && cmd ==
"spawn") ? parseIntOrDefault(args[2], 128) : ((args.size() >= 2 && cmd ==
"spawn_random") ? parseIntOrDefault(args[1], 128) : 128);
2643 glm::vec3 candidate = cameraPos;
2644 if (!sampleNavigablePoint(1.7f, 0.68f, candidate, std::max(1, attempts))) {
2645 candidate = world.startPosition();
2648 cameraPos = candidate;
2649 yaw = chooseBestSpawnYaw(cameraPos);
2651 updateCameraVectors();
2653 out << std::format(
"Spawned at random location ({:.2f}, {:.2f}, {:.2f})", cameraPos.x, cameraPos.y, cameraPos.z);
2654 logEnv(
"command: spawn_random");
2658 if (cmd ==
"reset" || cmd ==
"reset_collectibles") {
2659 std::vector<Collectible> &collectibles = world.collectibles();
2660 for (
size_t i = 0; i < collectibles.size(); ++i) {
2661 Collectible &obj = collectibles[i];
2663 obj.rotation = glm::vec3(0.0f);
2664 relocateCollectible(i, 2.0f, 128);
2666 resolveCollectibleClusters(2.0f, 4);
2668 out << std::format(
"Collectibles reset. Active collectibles: {}", world.activeCollectibles());
2669 logEnv(
"command: reset collectibles");
2673 if (cmd ==
"add_collectibles" || cmd ==
"add_collectables") {
2674 const int requested = (args.size() >= 2) ? parseIntOrDefault(args[1], 10) : 10;
2675 const int toAdd = std::clamp(requested, 1, 200);
2676 std::uniform_int_distribution<int> typeDist(0, 1);
2677 std::uniform_real_distribution<float> saturnScale(0.4f, 0.8f);
2678 std::uniform_real_distribution<float> saturnRotSpeed(5.0f, 15.0f);
2679 std::uniform_real_distribution<float> birdScale(0.3f, 0.5f);
2680 std::uniform_real_distribution<float> birdRotSpeed(20.0f, 60.0f);
2683 for (
int i = 0; i < toAdd; ++i) {
2685 obj.type = (typeDist(rng) == 0) ? Collectible::Type::Saturn : Collectible::Type::Bird;
2686 if (obj.type == Collectible::Type::Saturn) {
2687 const float scale = saturnScale(rng);
2688 obj.scale = glm::vec3(scale);
2689 obj.rotationSpeed = saturnRotSpeed(rng);
2690 obj.radius = saturnHitRadiusForScale(scale);
2691 obj.hitCenterOffset = saturnHitCenterOffsetForScale(scale);
2693 const float scale = birdScale(rng);
2694 obj.scale = glm::vec3(scale);
2695 obj.rotationSpeed = birdRotSpeed(rng);
2696 obj.radius = birdHitHalfSideForScale(scale);
2697 obj.hitCenterOffset = birdHitCenterOffsetForScale(scale);
2700 bool placed =
false;
2701 for (
int attempt = 0; attempt < 96; ++attempt) {
2702 const float y = (obj.type == Collectible::Type::Bird) ? birdGroundYForScale(obj.scale.x) : 2.5f;
2703 const float placementRadius = placementRadiusForCollectible(obj);
2704 glm::vec3 candidate{};
2705 if (!sampleNavigablePoint(y, placementRadius, candidate, 1)) {
2709 bool overlaps =
false;
2710 for (
const Collectible &existing : world.collectibles()) {
2711 if (!existing.active) {
2714 const float separation = std::max(5.0f, existing.radius + obj.radius + 0.2f);
2715 if (glm::length(existing.position - candidate) < separation) {
2722 obj.position = candidate;
2729 world.collectibles().push_back(obj);
2734 out << std::format(
"Added {} collectible(s). Active collectibles: {}", added, world.activeCollectibles());
2735 resolveCollectibleClusters(2.0f, 4);
2736 logEnv(std::format(
"command: add_collectibles requested={} added={}", toAdd, added));
2740 if (cmd ==
"status") {
2741 out << std::format(
"pos=({:.2f}, {:.2f}, {:.2f}) yaw={:.2f} pitch={:.2f}\n"
2742 "walls={} pillars={} collectibles(active/total)={}/{} bullets={} particles={} destroyed={}",
2748 world.walls().size(),
2749 world.pillars().size(),
2750 world.activeCollectibles(),
2751 world.collectibles().size(),
2753 explosionParticles.size(),
2758 if (cmd ==
"teleport") {
2759 if (args.size() < 4) {
2760 out <<
"Usage: teleport <x> <y> <z>";
2767 if (!tryParseFloat(args[1], x) || !tryParseFloat(args[2], y) || !tryParseFloat(args[3], z)) {
2768 out <<
"teleport: invalid numeric argument(s)";
2772 const glm::vec3 candidate{x, y, z};
2773 if (world.checkWallCollision(candidate, 0.68f) || world.checkPillarCollision(candidate, 0.68f)) {
2774 out <<
"teleport blocked: target intersects wall/pillar";
2778 cameraPos = candidate;
2779 out << std::format(
"Teleported to ({:.2f}, {:.2f}, {:.2f})", x, y, z);
2780 logEnv(
"command: teleport");
2784 if (cmd ==
"clear_bullets") {
2785 const std::size_t removed = bullets.size();
2787 out << std::format(
"Cleared {} bullet(s)", removed);
2791 if (cmd ==
"clear_fx") {
2792 const std::size_t removed = explosionParticles.size();
2793 explosionParticles.clear();
2794 out << std::format(
"Cleared {} particle effect(s)", removed);
2798 if (cmd ==
"set_fps") {
2799 if (args.size() < 2) {
2800 out << std::format(
"FPS overlay is currently {}. Usage: set_fps <on|off>", showFps ?
"on" :
"off");
2803 const std::string value = toLowerCopy(args[1]);
2804 if (value ==
"on" || value ==
"1" || value ==
"true") {
2806 out <<
"FPS overlay enabled";
2809 if (value ==
"off" || value ==
"0" || value ==
"false") {
2811 out <<
"FPS overlay disabled";
2815 out <<
"Usage: set_fps <on|off>";
2819 if (cmd ==
"regen_world") {
2820 const uint32_t seed = (args.size() >= 2) ?
static_cast<uint32_t
>(parseIntOrDefault(args[1],
static_cast<int>(
rng()))) :
rng();
2821 world.generate(seed);
2822 normalizeCollectiblesToModel();
2823 cameraPos = world.startPosition();
2824 yaw = chooseBestSpawnYaw(cameraPos);
2826 updateCameraVectors();
2828 explosionParticles.clear();
2831 out << std::format(
"Regenerated world with seed {} (walls={}, pillars={}, collectibles={})", seed, world.walls().size(), world.pillars().size(), world.collectibles().size());
2832 logEnv(std::format(
"command: regen_world seed={}", seed));
2836 if (cmd ==
"set_wall" || cmd ==
"set_floor" || cmd ==
"set_pillar" || cmd ==
"set_object" || cmd ==
"set_bullet") {
2837 if (args.size() < 2) {
2838 out << std::format(
"Usage: {} <shader.spv|full/path/to/shader.spv>", cmd);
2842 const std::string shaderPath = resolveShaderPath(args[1]);
2843 std::vector<char> shaderBytes;
2845 shaderBytes = loadSpv(shaderPath);
2846 }
catch (
const mxvk::Exception &e) {
2847 out << std::format(
"{}: failed to load shader '{}': {}", cmd, shaderPath, e.
text());
2851 if (shaderBytes.empty()) {
2852 out << std::format(
"{}: shader '{}' is empty", cmd, shaderPath);
2856 if (cmd ==
"set_wall") {
2857 wallFragSpv = shaderPath;
2858 rawWallRenderer.reloadFragShader(shaderBytes);
2859 out << std::format(
"Wall shader reloaded from {}", shaderPath);
2860 }
else if (cmd ==
"set_floor") {
2861 floorFragSpv = shaderPath;
2862 floorModel.setShaders(
this, modelVertSpv, shaderPath);
2863 out << std::format(
"Floor shader reloaded from {}", shaderPath);
2864 }
else if (cmd ==
"set_pillar") {
2865 pillarFragSpv = shaderPath;
2866 rawPillarRenderer.reloadFragShader(shaderBytes);
2867 out << std::format(
"Pillar shader reloaded from {}", shaderPath);
2868 }
else if (cmd ==
"set_object") {
2869 objectFragSpv = shaderPath;
2870 saturnModel.setShaders(
this, modelVertSpv, shaderPath);
2871 birdModel.setShaders(
this, modelVertSpv, shaderPath);
2872 out << std::format(
"Object shader reloaded from {}", shaderPath);
2873 }
else if (cmd ==
"set_bullet") {
2874 bulletFragSpv = shaderPath;
2875 bulletModel.setShaders(
this, modelVertSpv, shaderPath);
2876 out << std::format(
"Bullet shader reloaded from {}", shaderPath);
2879 logEnv(std::format(
"command: {} shader={}", cmd, shaderPath));
2883 if (cmd ==
"list_shaders") {
2884 const std::array<std::pair<std::string_view, std::string>, 11> shaders{{
2885 {
"wall.frag", resolveShaderPath(
"wall.frag.spv")},
2886 {
"floor.frag", resolveShaderPath(
"floor.frag.spv")},
2887 {
"pillar.frag", resolveShaderPath(
"pillar.frag.spv")},
2888 {
"object.frag", resolveShaderPath(
"object.frag.spv")},
2889 {
"bullet.frag", resolveShaderPath(
"bullet.frag.spv")},
2890 {
"particle.frag", resolveShaderPath(
"particle.frag.spv")},
2891 {
"particle_points.frag", resolveShaderPath(
"particle_points.frag.spv")},
2892 {
"bubble.frag", resolveShaderPath(
"bubble.frag.spv")},
2893 {
"floor_kale.frag", resolveShaderPath(
"floor_kale.frag.spv")},
2894 {
"floor_swirl.frag", resolveShaderPath(
"floor_swirl.frag.spv")},
2895 {
"floor_twist.frag", resolveShaderPath(
"floor_twist.frag.spv")},
2898 out <<
"Available shaders:\n";
2899 for (
const auto &[name, path] : shaders) {
2900 out << std::format(
" {:<20} {}\n", name, path);
2902 out << std::format(
"Current bindings:\n"
2913 if (!postProcessingShaders.empty()) {
2914 out << std::format(
" post_fx {} of {} {}\n", postProcessingShaderIndex + 1, postProcessingShaders.size(), currentPostProcessingShader());
2922 void appendConsoleHelp(std::ostream &out)
const override {
2923 out <<
"\nWalk debug commands:\n"
2924 <<
" spawn_random [attempts] Spawn player at random valid location\n"
2925 <<
" spawn random [attempts] Alias for spawn_random\n"
2926 <<
" reset Reset all collectibles to active\n"
2927 <<
" add_collectibles [count] Add random collectibles (alias: add_collectables)\n"
2928 <<
" status Print camera/world/debug state\n"
2929 <<
" teleport <x> <y> <z> Teleport player if destination is valid\n"
2930 <<
" clear_bullets Remove all active bullets\n"
2931 <<
" clear_fx Remove all active explosion particles\n"
2932 <<
" set_fps <on|off> Toggle FPS overlay\n"
2933 <<
" set_wall <shader.spv> Reload wall fragment shader\n"
2934 <<
" set_floor <shader.spv> Reload floor fragment shader\n"
2935 <<
" set_pillar <shader.spv> Reload pillar fragment shader\n"
2936 <<
" set_object <shader.spv> Reload object fragment shader\n"
2937 <<
" set_bullet <shader.spv> Reload bullet fragment shader\n"
2938 <<
" list_shaders Print available shaders and current bindings\n"
2939 <<
" R/T Select previous/next post-processing shader\n"
2940 <<
" regen_world [seed] Regenerate maze, pillars, and collectibles";
2943 void logEnv(
const std::string &message) { print(std::format(
"[walk] {}", message), {255, 100, 255, 255}); }
2945 [[nodiscard]]
static std::string
trimLine(std::string value) {
2946 auto begin = value.begin();
2947 while (begin != value.end() && std::isspace(
static_cast<unsigned char>(*begin)) != 0) {
2951 auto end = value.end();
2952 while (end != begin && std::isspace(
static_cast<unsigned char>(*(end - 1))) != 0) {
2956 return std::string(begin, end);
2959 [[nodiscard]]
static std::string
joinPath(
const std::string &base,
const std::string &file) {
2960 const std::filesystem::path filePath(file);
2961 if (base.empty() || filePath.is_absolute()) {
2962 return filePath.string();
2964 return (std::filesystem::path(base) / filePath).string();
2967 [[nodiscard]]
static std::string resolvePostProcessingShaderEntry(
const std::string &shaderPath,
const std::string &entry) {
2968 const std::filesystem::path entryPath(entry);
2969 if (entryPath.is_absolute() || entryPath.extension() ==
".spv") {
2970 return joinPath(shaderPath, entry);
2973 std::filesystem::path spvEntry = entryPath.parent_path() /
"spv" / entryPath.stem();
2974 spvEntry.replace_extension(
".spv");
2975 const std::filesystem::path spvPath = std::filesystem::path(shaderPath) / spvEntry;
2976 if (std::filesystem::exists(spvPath)) {
2977 return spvPath.string();
2980 std::filesystem::path siblingEntry = entryPath;
2981 siblingEntry.replace_extension(
".spv");
2982 const std::filesystem::path siblingSpvPath = std::filesystem::path(shaderPath) / siblingEntry;
2983 if (std::filesystem::exists(siblingSpvPath)) {
2984 return siblingSpvPath.string();
2987 return joinPath(shaderPath, entry);
2990 void loadPostProcessingShaderIndex(
const std::string &shaderPath) {
2991 postProcessingShaderPath = shaderPath;
2992 postProcessingShaders.clear();
2993 postProcessingShaderIndex = 0;
2995 if (postProcessingShaderPath.empty()) {
2996 setPostProcessingEnabled(
false);
3000 const std::string indexPath =
joinPath(postProcessingShaderPath,
"index.txt");
3001 std::ifstream input(indexPath);
3002 if (!input.is_open()) {
3003 throw mxvk::Exception(
"walk_post: failed to open post-processing shader index: " + indexPath);
3007 while (std::getline(input, line)) {
3008 const size_t comment = line.find(
'#');
3009 if (comment != std::string::npos) {
3010 line.resize(comment);
3013 const std::string entry =
trimLine(line);
3014 if (entry.empty()) {
3018 const std::string shaderFile = resolvePostProcessingShaderEntry(postProcessingShaderPath, entry);
3019 if (std::filesystem::path(shaderFile).extension() !=
".spv") {
3020 throw mxvk::Exception(
"walk_post: post-processing shader entry is not SPIR-V: " + entry);
3022 if (!std::filesystem::exists(shaderFile)) {
3023 throw mxvk::Exception(
"walk_post: post-processing shader listed in index.txt was not found: " + shaderFile);
3026 postProcessingShaders.push_back(shaderFile);
3029 if (postProcessingShaders.empty()) {
3030 throw mxvk::Exception(
"walk_post: post-processing shader index did not list any shaders: " + indexPath);
3033 logEnv(std::format(
"loaded {} post-processing shader(s) from {}", postProcessingShaders.size(), indexPath));
3036 void setPostProcessingShaderIndex(
const int index) {
3037 if (postProcessingShaders.empty()) {
3038 postProcessingShaderIndex = 0;
3042 const int shaderCount =
static_cast<int>(postProcessingShaders.size());
3043 postProcessingShaderIndex = index % shaderCount;
3044 if (postProcessingShaderIndex < 0) {
3045 postProcessingShaderIndex += shaderCount;
3049 [[nodiscard]]
const std::string ¤tPostProcessingShader()
const {
return postProcessingShaders[
static_cast<std::size_t
>(postProcessingShaderIndex)]; }
3051 void applyPostProcessingShaderSelection() {
3052 if (postProcessingShaders.empty()) {
3056 if (postProcessingSprite !=
nullptr) {
3057 postProcessingSprite->setFragmentShaderPath(currentPostProcessingShader());
3058 postProcessingFrameCount = 0;
3059 postProcessingStartTime = std::chrono::steady_clock::now();
3060 previousPostProcessingTime = postProcessingStartTime;
3061 setPostProcessingEnabled(
true);
3062 logEnv(std::format(
"post-processing shader {} of {}: {}", postProcessingShaderIndex + 1, postProcessingShaders.size(), currentPostProcessingShader()));
3066 postProcessingSprite = attachPostProcessingShader(currentPostProcessingShader(), 1.0f, 1.0f, 1.0f, 0.0f);
3067 if (postProcessingSprite !=
nullptr) {
3068 postProcessingSprite->enableExtendedUBO();
3069 postProcessingFrameCount = 0;
3070 postProcessingStartTime = std::chrono::steady_clock::now();
3071 previousPostProcessingTime = postProcessingStartTime;
3073 setPostProcessingEnabled(
true);
3074 logEnv(std::format(
"post-processing shader {} of {}: {}", postProcessingShaderIndex + 1, postProcessingShaders.size(), currentPostProcessingShader()));
3077 void selectPostProcessingShader(
const int direction) {
3078 if (postProcessingShaders.empty()) {
3082 setPostProcessingShaderIndex(postProcessingShaderIndex + direction);
3083 if (getDevice() != VK_NULL_HANDLE) {
3084 vkDeviceWaitIdle(getDevice());
3086 applyPostProcessingShaderSelection();
3089 void updatePostProcessingShaderUniforms(
const float deltaTime) {
3090 if (postProcessingSprite ==
nullptr || postProcessingShaders.empty()) {
3094 const auto now = std::chrono::steady_clock::now();
3095 const float elapsed = std::chrono::duration<float>(now - postProcessingStartTime).count();
3096 const float frameDelta = std::max(deltaTime, std::chrono::duration<float>(now - previousPostProcessingTime).count());
3097 previousPostProcessingTime = now;
3098 ++postProcessingFrameCount;
3100 float mouseX = 0.0f;
3101 float mouseY = 0.0f;
3102 const SDL_MouseButtonFlags mouseButtons = SDL_GetMouseState(&mouseX, &mouseY);
3103 const float mousePressed = (mouseButtons & SDL_BUTTON_LMASK) != 0U ? 1.0f : 0.0f;
3104 const float frameRate = frameDelta > 0.0001f ? (1.0f / frameDelta) : 0.0f;
3106 setPostProcessingShaderParams(1.0f, 1.0f, 1.0f, elapsed);
3107 postProcessingSprite->setMouseState(mouseX, mouseY, mousePressed, mousePressed);
3108 postProcessingSprite->setUniform0(1.0f, 1.0f, 1.0f, 0.0f);
3109 postProcessingSprite->setUniform1(frameDelta, 0.0f, 0.0f, frameRate);
3110 postProcessingSprite->setUniform2(
static_cast<float>(postProcessingFrameCount), elapsed, 48000.0f, 0.0f);
3111 postProcessingSprite->setUniform3(0.0f, 0.0f, 0.0f, 0.0f);
3119 [[nodiscard]] std::string resolveShaderPath(
const std::string &name)
const {
3120 const std::string runtimePath = shaderRoot +
"/" + name;
3121 if (std::filesystem::exists(runtimePath)) {
3127 [[nodiscard]]
static std::string toLowerCopy(std::string value) {
3128 std::transform(value.begin(), value.end(), value.begin(), [](
const unsigned char ch) { return static_cast<char>(std::tolower(ch)); });
3132 [[nodiscard]]
static int parseIntOrDefault(
const std::string &text,
const int fallback) {
3133 int value = fallback;
3134 const auto begin = text.data();
3135 const auto end = text.data() + text.size();
3136 const auto [ptr, ec] = std::from_chars(begin, end, value);
3137 if (ec != std::errc{} || ptr != end) {
3143 [[nodiscard]]
static bool tryParseFloat(
const std::string &text,
float &outValue) {
3146 const float value = std::stof(text, &parsed);
3147 if (parsed != text.size()) {
3157 bool sampleNavigablePoint(
const float y,
const float radius, glm::vec3 &outPoint,
const int maxAttempts) {
3158 float minX = -50.0f;
3160 float minZ = -50.0f;
3163 bool haveBounds =
false;
3164 for (
const WallSegment &wall : world.walls()) {
3166 minX = std::min(wall.start.x, wall.end.x);
3167 maxX = std::max(wall.start.x, wall.end.x);
3168 minZ = std::min(wall.start.z, wall.end.z);
3169 maxZ = std::max(wall.start.z, wall.end.z);
3172 minX = std::min(minX, std::min(wall.start.x, wall.end.x));
3173 maxX = std::max(maxX, std::max(wall.start.x, wall.end.x));
3174 minZ = std::min(minZ, std::min(wall.start.z, wall.end.z));
3175 maxZ = std::max(maxZ, std::max(wall.start.z, wall.end.z));
3180 outPoint = world.startPosition();
3185 const float margin = std::max(0.8f, radius + 0.5f);
3191 if (minX > maxX || minZ > maxZ) {
3192 outPoint = world.startPosition();
3197 std::uniform_real_distribution<float> distX(minX, maxX);
3198 std::uniform_real_distribution<float> distZ(minZ, maxZ);
3199 for (
int i = 0; i < maxAttempts; ++i) {
3200 const glm::vec3 candidate{distX(rng), y, distZ(rng)};
3201 if (!world.checkWallCollision(candidate, radius) && !world.checkPillarCollision(candidate, radius)) {
3202 outPoint = candidate;
3210 [[nodiscard]]
static const char *collectibleTypeName(Collectible::Type type)
noexcept {
return type == Collectible::Type::Saturn ?
"saturn" :
"bird"; }
3212 void loadModel(mxvk::VKAbstractModel &model,
const std::string &modelPath,
const std::string &textureManifest,
const std::string &textureBase,
const std::string &vertSpv,
const std::string &fragSpv,
bool backfaceCulling =
false) {
3213 logEnv(std::format(
"loading model '{}'", modelPath));
3214 model.
load(
this, modelPath, textureManifest, textureBase, 1.0f);
3217 logEnv(std::format(
"model ready '{}'", modelPath));
3220 void cleanupModels() {
3221 floorModel.cleanup(
this);
3222 rawWallRenderer.cleanup(
this);
3223 rawPillarRenderer.cleanup(
this);
3225 saturnModel.cleanup(
this);
3226 birdModel.cleanup(
this);
3227 blasterModel.cleanup(
this);
3228 bulletModel.cleanup(
this);
3231 [[nodiscard]] uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties)
const {
3232 VkPhysicalDeviceMemoryProperties memProperties{};
3233 vkGetPhysicalDeviceMemoryProperties(getPhysicalDevice(), &memProperties);
3234 for (uint32_t i = 0; i < memProperties.memoryTypeCount; ++i) {
3235 if ((typeFilter & (1u << i)) != 0u && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) {
3239 throw mxvk::Exception(
"walk: failed to find suitable Vulkan memory type for point particles");
3242 void initializePointParticles() {
3243 if (!ensureRenderResources()) {
3244 throw mxvk::Exception(
"walk: render resources unavailable for point particles");
3247 destroyPointParticles();
3249 VkBufferCreateInfo bufferInfo{};
3250 bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
3251 bufferInfo.size = maxPointVertices *
sizeof(ParticlePointVertex);
3252 bufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
3253 bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
3254 if (vkCreateBuffer(getDevice(), &bufferInfo,
nullptr, &pointVertexBuffer) != VK_SUCCESS) {
3255 throw mxvk::Exception(
"walk: failed to create point particle vertex buffer");
3258 VkMemoryRequirements memReq{};
3259 vkGetBufferMemoryRequirements(getDevice(), pointVertexBuffer, &memReq);
3260 VkMemoryAllocateInfo allocInfo{};
3261 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
3262 allocInfo.allocationSize = memReq.size;
3263 allocInfo.memoryTypeIndex = findMemoryType(memReq.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
3264 if (vkAllocateMemory(getDevice(), &allocInfo,
nullptr, &pointVertexMemory) != VK_SUCCESS) {
3265 throw mxvk::Exception(
"walk: failed to allocate point particle vertex memory");
3267 if (vkBindBufferMemory(getDevice(), pointVertexBuffer, pointVertexMemory, 0) != VK_SUCCESS) {
3268 throw mxvk::Exception(
"walk: failed to bind point particle vertex memory");
3270 if (vkMapMemory(getDevice(), pointVertexMemory, 0, bufferInfo.size, 0, &pointVertexMapped) != VK_SUCCESS) {
3271 throw mxvk::Exception(
"walk: failed to map point particle vertex memory");
3274 rebuildPointParticlePipeline();
3276 destroyPointParticles();
3281 void rebuildPointParticlePipeline() {
3282 if (pointPipeline != VK_NULL_HANDLE) {
3283 vkDestroyPipeline(getDevice(), pointPipeline,
nullptr);
3284 pointPipeline = VK_NULL_HANDLE;
3286 if (pointPipelineLayout != VK_NULL_HANDLE) {
3287 vkDestroyPipelineLayout(getDevice(), pointPipelineLayout,
nullptr);
3288 pointPipelineLayout = VK_NULL_HANDLE;
3291 if (getSwapchainFormat() == VK_FORMAT_UNDEFINED) {
3295 const std::vector<char> vertBytes = loadSpv(pointParticleVertSpv);
3296 const std::vector<char> fragBytes = loadSpv(pointParticleFragSpv);
3300 VkPipelineShaderStageCreateInfo vertStage{};
3301 vertStage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
3302 vertStage.stage = VK_SHADER_STAGE_VERTEX_BIT;
3303 vertStage.module = vertModule;
3304 vertStage.pName =
"main";
3306 VkPipelineShaderStageCreateInfo fragStage{};
3307 fragStage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
3308 fragStage.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
3309 fragStage.module = fragModule;
3310 fragStage.pName =
"main";
3311 const std::array<VkPipelineShaderStageCreateInfo, 2> stages = {vertStage, fragStage};
3313 VkVertexInputBindingDescription binding{};
3314 binding.binding = 0;
3315 binding.stride =
sizeof(ParticlePointVertex);
3316 binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
3318 std::array<VkVertexInputAttributeDescription, 3> attrs{};
3319 attrs[0] = {0, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(ParticlePointVertex, pos)};
3320 attrs[1] = {1, 0, VK_FORMAT_R32G32B32A32_SFLOAT, offsetof(ParticlePointVertex, color)};
3321 attrs[2] = {2, 0, VK_FORMAT_R32_SFLOAT, offsetof(ParticlePointVertex, size)};
3323 VkPipelineVertexInputStateCreateInfo vertexInput{};
3324 vertexInput.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
3325 vertexInput.vertexBindingDescriptionCount = 1;
3326 vertexInput.pVertexBindingDescriptions = &binding;
3327 vertexInput.vertexAttributeDescriptionCount =
static_cast<uint32_t
>(attrs.size());
3328 vertexInput.pVertexAttributeDescriptions = attrs.data();
3330 VkPipelineInputAssemblyStateCreateInfo inputAssembly{};
3331 inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
3332 inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_POINT_LIST;
3334 const std::array<VkDynamicState, 2> dynamicStates = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
3335 VkPipelineDynamicStateCreateInfo dynamicInfo{};
3336 dynamicInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
3337 dynamicInfo.dynamicStateCount =
static_cast<uint32_t
>(dynamicStates.size());
3338 dynamicInfo.pDynamicStates = dynamicStates.data();
3340 VkPipelineViewportStateCreateInfo viewportState{};
3341 viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
3342 viewportState.viewportCount = 1;
3343 viewportState.scissorCount = 1;
3345 VkPipelineRasterizationStateCreateInfo rasterizer{};
3346 rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
3347 rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
3348 rasterizer.cullMode = VK_CULL_MODE_NONE;
3349 rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE;
3350 rasterizer.lineWidth = 1.0f;
3352 VkPipelineMultisampleStateCreateInfo multisample{};
3353 multisample.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
3354 multisample.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
3356 VkPipelineDepthStencilStateCreateInfo depthStencil{};
3357 depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
3358 depthStencil.depthTestEnable = VK_FALSE;
3359 depthStencil.depthWriteEnable = VK_FALSE;
3360 depthStencil.depthCompareOp = VK_COMPARE_OP_LESS_OR_EQUAL;
3362 VkPipelineColorBlendAttachmentState blendAttachment{};
3363 blendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
3364 blendAttachment.blendEnable = VK_TRUE;
3365 blendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
3366 blendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE;
3367 blendAttachment.colorBlendOp = VK_BLEND_OP_ADD;
3368 blendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
3369 blendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
3370 blendAttachment.alphaBlendOp = VK_BLEND_OP_ADD;
3372 VkPipelineColorBlendStateCreateInfo colorBlend{};
3373 colorBlend.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
3374 colorBlend.attachmentCount = 1;
3375 colorBlend.pAttachments = &blendAttachment;
3377 VkPushConstantRange pushRange{};
3378 pushRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
3379 pushRange.offset = 0;
3380 pushRange.size =
sizeof(glm::mat4);
3382 VkPipelineLayoutCreateInfo layoutInfo{};
3383 layoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
3384 layoutInfo.pushConstantRangeCount = 1;
3385 layoutInfo.pPushConstantRanges = &pushRange;
3386 if (vkCreatePipelineLayout(getDevice(), &layoutInfo,
nullptr, &pointPipelineLayout) != VK_SUCCESS) {
3387 vkDestroyShaderModule(getDevice(), fragModule,
nullptr);
3388 vkDestroyShaderModule(getDevice(), vertModule,
nullptr);
3389 throw mxvk::Exception(
"walk: failed to create point particle pipeline layout");
3392 const VkFormat colorFormat = getSwapchainFormat();
3393 const VkFormat depthFormat = getDepthFormat();
3394 VkPipelineRenderingCreateInfo renderingInfo{};
3395 renderingInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO;
3396 renderingInfo.colorAttachmentCount = 1;
3397 renderingInfo.pColorAttachmentFormats = &colorFormat;
3398 if (depthFormat != VK_FORMAT_UNDEFINED) {
3399 renderingInfo.depthAttachmentFormat = depthFormat;
3402 VkGraphicsPipelineCreateInfo pipelineInfo{};
3403 pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
3404 pipelineInfo.pNext = &renderingInfo;
3405 pipelineInfo.stageCount =
static_cast<uint32_t
>(stages.size());
3406 pipelineInfo.pStages = stages.data();
3407 pipelineInfo.pVertexInputState = &vertexInput;
3408 pipelineInfo.pInputAssemblyState = &inputAssembly;
3409 pipelineInfo.pViewportState = &viewportState;
3410 pipelineInfo.pRasterizationState = &rasterizer;
3411 pipelineInfo.pMultisampleState = &multisample;
3412 pipelineInfo.pDepthStencilState = &depthStencil;
3413 pipelineInfo.pColorBlendState = &colorBlend;
3414 pipelineInfo.pDynamicState = &dynamicInfo;
3415 pipelineInfo.layout = pointPipelineLayout;
3416 pipelineInfo.renderPass = VK_NULL_HANDLE;
3418 if (vkCreateGraphicsPipelines(getDevice(), VK_NULL_HANDLE, 1, &pipelineInfo,
nullptr, &pointPipeline) != VK_SUCCESS) {
3419 vkDestroyShaderModule(getDevice(), fragModule,
nullptr);
3420 vkDestroyShaderModule(getDevice(), vertModule,
nullptr);
3421 throw mxvk::Exception(
"walk: failed to create point particle graphics pipeline");
3424 vkDestroyShaderModule(getDevice(), fragModule,
nullptr);
3425 vkDestroyShaderModule(getDevice(), vertModule,
nullptr);
3428 void destroyPointParticles() {
3429 if (pointPipeline != VK_NULL_HANDLE) {
3430 vkDestroyPipeline(getDevice(), pointPipeline,
nullptr);
3431 pointPipeline = VK_NULL_HANDLE;
3433 if (pointPipelineLayout != VK_NULL_HANDLE) {
3434 vkDestroyPipelineLayout(getDevice(), pointPipelineLayout,
nullptr);
3435 pointPipelineLayout = VK_NULL_HANDLE;
3437 if (pointVertexMapped !=
nullptr) {
3438 vkUnmapMemory(getDevice(), pointVertexMemory);
3439 pointVertexMapped =
nullptr;
3441 if (pointVertexBuffer != VK_NULL_HANDLE) {
3442 vkDestroyBuffer(getDevice(), pointVertexBuffer,
nullptr);
3443 pointVertexBuffer = VK_NULL_HANDLE;
3445 if (pointVertexMemory != VK_NULL_HANDLE) {
3446 vkFreeMemory(getDevice(), pointVertexMemory,
nullptr);
3447 pointVertexMemory = VK_NULL_HANDLE;
3451 void renderPointParticles(VkCommandBuffer cmd,
const glm::mat4 &view,
const glm::mat4 &proj) {
3452 if (pointPipeline == VK_NULL_HANDLE || pointPipelineLayout == VK_NULL_HANDLE || pointVertexMapped ==
nullptr) {
3456 std::vector<ParticlePointVertex> vertices{};
3457 vertices.reserve(2048);
3459 for (
const Projectile &bullet : bullets) {
3460 if (!bullet.active) {
3463 for (
const Projectile::TrailPoint &point : bullet.trail) {
3464 const float life = glm::clamp(point.lifetime / point.maxLifetime, 0.0f, 1.0f);
3465 const float fade = 1.0f - life;
3466 vertices.push_back({point.position, glm::vec4(1.0f, 0.2f, 0.0f, fade * 0.8f), 12.0f});
3470 for (
const ExplosionParticle &particle : explosionParticles) {
3471 if (!particle.active) {
3474 const float life = glm::clamp(particle.lifetime / particle.maxLifetime, 0.0f, 1.0f);
3475 const float fade = 1.0f - life;
3476 const float sizePx = glm::clamp(particle.size * 320.0f, 12.0f, 160.0f);
3477 vertices.push_back({particle.position, glm::vec4(particle.color, fade), sizePx});
3480 if (vertices.empty()) {
3484 if (vertices.size() > maxPointVertices) {
3485 vertices.resize(maxPointVertices);
3487 std::memcpy(pointVertexMapped, vertices.data(), vertices.size() *
sizeof(ParticlePointVertex));
3489 const VkBuffer vb = pointVertexBuffer;
3490 const VkDeviceSize offset = 0;
3491 vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pointPipeline);
3492 vkCmdBindVertexBuffers(cmd, 0, 1, &vb, &offset);
3493 const glm::mat4 vp = proj * view;
3494 vkCmdPushConstants(cmd, pointPipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0,
sizeof(glm::mat4), &vp);
3495 vkCmdDraw(cmd,
static_cast<uint32_t
>(vertices.size()), 1, 0, 0);
3498 bool openGamepad(SDL_JoystickID
id) {
3499 if (gamepad !=
nullptr && gamepadId ==
id) {
3502 if (gamepad !=
nullptr) {
3503 SDL_CloseGamepad(gamepad);
3507 gamepad = SDL_OpenGamepad(
id);
3508 if (gamepad ==
nullptr) {
3509 logEnv(std::format(
"failed to open gamepad id={}",
static_cast<int>(
id)));
3513 const char *padName = SDL_GetGamepadName(gamepad);
3514 logEnv(std::format(
"gamepad connected: id={} name='{}'",
static_cast<int>(
id), padName !=
nullptr ? padName :
"unknown"));
3518 void tryOpenFirstGamepad() {
3519 if (gamepad !=
nullptr) {
3523 SDL_JoystickID *ids = SDL_GetGamepads(&count);
3524 if (ids ==
nullptr || count <= 0) {
3525 if (ids !=
nullptr) {
3530 openGamepad(ids[0]);
3534 [[nodiscard]]
static glm::mat4 composeNormalizedModel(
const mxvk::VKAbstractModel &model,
const glm::mat4 &world) {
3535 glm::mat4 transform = world;
3536 transform = transform * glm::scale(glm::mat4(1.0f), glm::vec3(model.
modelRenderScale()));
3537 transform = transform * glm::translate(glm::mat4(1.0f), model.
modelCenterOffset());
3544 [[nodiscard]]
static glm::mat4 composeRecenteredModel(
const mxvk::VKAbstractModel &model,
const glm::mat4 &world) {
return world * glm::translate(glm::mat4(1.0f), model.
modelCenterOffset()); }
3546 void renderModel(VkCommandBuffer cmd, uint32_t imageIndex, mxvk::VKAbstractModel &model,
const glm::mat4 &world,
const glm::mat4 &view,
const glm::mat4 &proj,
const glm::vec4 &fx,
bool autoNormalize =
true) {
3547 mxvk::UniformBufferObject ubo{};
3548 ubo.
model = autoNormalize ? composeNormalizedModel(model, world) : composeRecenteredModel(model, world);
3553 model.
render(cmd, imageIndex,
false);
3556 void renderRawModel(VkCommandBuffer cmd, uint32_t imageIndex, mxvk::VKAbstractModel &model,
const glm::mat4 &world,
const glm::mat4 &view,
const glm::mat4 &proj,
const glm::vec4 &fx) {
3557 mxvk::UniformBufferObject ubo{};
3563 model.
render(cmd, imageIndex,
false);
3566 void updateCameraVectors() {
3567 glm::vec3 front(0.0f);
3568 front.x = std::cos(glm::radians(yaw)) * std::cos(glm::radians(pitch));
3569 front.y = std::sin(glm::radians(pitch));
3570 front.z = std::sin(glm::radians(yaw)) * std::cos(glm::radians(pitch));
3571 cameraFront = glm::normalize(front);
3574 void buildCameraBasis(glm::vec3 &forward, glm::vec3 &right, glm::vec3 &up)
const {
3575 forward = cameraFront;
3576 if (glm::length(forward) <= 1e-5f) {
3577 forward = glm::vec3(0.0f, 0.0f, -1.0f);
3579 forward = glm::normalize(forward);
3582 right = glm::cross(forward, glm::vec3(0.0f, 1.0f, 0.0f));
3583 if (glm::length(right) <= 1e-5f) {
3584 right = glm::vec3(1.0f, 0.0f, 0.0f);
3586 right = glm::normalize(right);
3589 up = glm::cross(right, forward);
3590 if (glm::length(up) <= 1e-5f) {
3591 up = glm::vec3(0.0f, 1.0f, 0.0f);
3593 up = glm::normalize(up);
3597 [[nodiscard]] glm::vec3 blasterMuzzleTipPosition()
const {
3598 glm::vec3 forward(0.0f);
3599 glm::vec3 right(0.0f);
3601 buildCameraBasis(forward, right, up);
3602 return cameraPos + (forward * 0.55f) + (right * 0.18f) - (up * 0.12f);
3605 [[nodiscard]] glm::vec3 projectileSpawnPosition()
const {
3606 glm::vec3 forward(0.0f);
3607 glm::vec3 right(0.0f);
3609 buildCameraBasis(forward, right, up);
3610 constexpr float projectileForwardOffset = 0.015f;
3611 return blasterMuzzleTipPosition() + (forward * projectileForwardOffset);
3614 [[nodiscard]] glm::mat4 blasterWorldTransform()
const {
3615 glm::vec3 forward(0.0f);
3616 glm::vec3 right(0.0f);
3618 buildCameraBasis(forward, right, up);
3620 constexpr float blasterScale = 0.45f;
3621 constexpr glm::vec3 localMuzzle(0.95f, 0.09f, 0.0f);
3622 const glm::vec3 desiredMuzzle = blasterMuzzleTipPosition();
3623 const glm::vec3 origin = desiredMuzzle - (forward * (localMuzzle.x * blasterScale)) - (up * (localMuzzle.y * blasterScale)) - (right * (localMuzzle.z * blasterScale));
3625 glm::mat4 world(1.0f);
3626 world[0] = glm::vec4(forward * blasterScale, 0.0f);
3627 world[1] = glm::vec4(up * blasterScale, 0.0f);
3628 world[2] = glm::vec4(right * blasterScale, 0.0f);
3629 world[3] = glm::vec4(origin, 1.0f);
3633 [[nodiscard]]
float viewDistanceInDirection(
const glm::vec3 &origin,
const glm::vec3 &direction)
const {
3634 const glm::vec3 dir = glm::normalize(glm::vec3(direction.x, 0.0f, direction.z));
3635 constexpr float maxDistance = 14.0f;
3636 constexpr float step = 0.35f;
3637 constexpr float probeRadius = 0.30f;
3638 for (
float d = step; d <= maxDistance; d += step) {
3639 const glm::vec3 point = origin + (dir * d);
3640 if (world.checkWallCollision(point, probeRadius) || world.checkPillarCollision(point, probeRadius)) {
3647 [[nodiscard]]
float chooseBestSpawnYaw(
const glm::vec3 &origin)
const {
3648 constexpr float pi = 3.14159265358979323846f;
3649 constexpr int sampleCount = 48;
3650 float bestDistance = -1.0f;
3651 float bestYaw = yaw;
3652 for (
int i = 0; i < sampleCount; ++i) {
3653 const float angle = (-pi) + (2.0f * pi *
static_cast<float>(i) /
static_cast<float>(sampleCount));
3654 const glm::vec3 dir(std::cos(angle), 0.0f, std::sin(angle));
3655 const float dist = viewDistanceInDirection(origin, dir);
3656 if (dist > bestDistance) {
3657 bestDistance = dist;
3658 bestYaw = glm::degrees(angle);
3664 void updatePlayer(
float deltaTime) {
3665 const bool *keys = SDL_GetKeyboardState(
nullptr);
3666 glm::vec3 horizontalFront = glm::normalize(glm::vec3(cameraFront.x, 0.0f, cameraFront.z));
3667 if (glm::length(horizontalFront) < 0.0001f) {
3668 horizontalFront = glm::vec3(0.0f, 0.0f, -1.0f);
3670 const glm::vec3 right = glm::normalize(glm::cross(horizontalFront, glm::vec3(0.0f, 1.0f, 0.0f)));
3672 glm::vec3 desired = cameraPos;
3673 bool sprint = keys[SDL_SCANCODE_LSHIFT] != 0;
3674 if (gamepad !=
nullptr && SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_LEFT_STICK)) {
3677 const float cameraSpeed = 0.2f;
3678 const float speed = sprint ? cameraSpeed * 2.0f : cameraSpeed;
3679 const float frameScale = deltaTime * 60.0f;
3680 const float moveStep = speed * frameScale;
3682 if (keys[SDL_SCANCODE_W]) {
3683 desired += horizontalFront * moveStep;
3685 if (keys[SDL_SCANCODE_S]) {
3686 desired -= horizontalFront * moveStep;
3688 if (keys[SDL_SCANCODE_A]) {
3689 desired -= right * moveStep;
3691 if (keys[SDL_SCANCODE_D]) {
3692 desired += right * moveStep;
3695 if (gamepad !=
nullptr) {
3696 const Sint16 leftX = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_LEFTX);
3697 const Sint16 leftY = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_LEFTY);
3698 if (std::abs(leftX) > stickDeadZone) {
3699 desired += moveStep * (
static_cast<float>(leftX) / 32768.0f) * right;
3701 if (std::abs(leftY) > stickDeadZone) {
3702 desired -= moveStep * (
static_cast<float>(leftY) / 32768.0f) * horizontalFront;
3705 const Sint16 rightX = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHTX);
3706 const Sint16 rightY = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHTY);
3707 if (std::abs(rightX) > stickDeadZone || std::abs(rightY) > stickDeadZone) {
3708 yaw += (
static_cast<float>(rightX) / 32768.0f) * controllerLookSensitivity;
3709 pitch -= (
static_cast<float>(rightY) / 32768.0f) * controllerLookSensitivity;
3710 pitch = glm::clamp(pitch, -89.0f, 89.0f);
3711 updateCameraVectors();
3715 constexpr float playerRadius = 0.5f;
3716 constexpr float cameraStandOff = 0.18f;
3717 const float collisionRadius = playerRadius + cameraStandOff;
3718 const auto isBlocked = [
this, collisionRadius](
const glm::vec3 &position) {
return world.checkWallCollision(position, collisionRadius) || world.checkPillarCollision(position, collisionRadius); };
3720 if (!isBlocked(desired)) {
3721 cameraPos = desired;
3725 glm::vec3 tryX = cameraPos;
3727 if (!isBlocked(tryX)) {
3728 cameraPos.x = tryX.x;
3731 glm::vec3 tryZ = cameraPos;
3733 if (!isBlocked(tryZ)) {
3734 cameraPos.z = tryZ.z;
3738 const bool crouch = keys[SDL_SCANCODE_LCTRL] != 0;
3739 const float minHeight = crouch ? 0.8f : 1.7f;
3740 if (keys[SDL_SCANCODE_SPACE] && cameraPos.y <= minHeight + 0.01f) {
3741 jumpVelocity = 0.3f;
3744 cameraPos.y += jumpVelocity * deltaTime * 60.0f;
3745 jumpVelocity -= gravity * deltaTime * 60.0f;
3746 if (cameraPos.y < minHeight) {
3747 cameraPos.y = minHeight;
3748 jumpVelocity = 0.0f;
3752 void fireProjectile() {
3753 emitMuzzleParticles();
3755 Projectile bullet{};
3756 bullet.position = projectileSpawnPosition();
3757 bullet.direction = glm::normalize(cameraFront);
3758 bullets.push_back(bullet);
3759 logEnv(std::format(
"projectile fired from ({:.2f}, {:.2f}, {:.2f}) dir=({:.2f}, {:.2f}, {:.2f}) active_bullets={}", bullet.position.
x, bullet.position.
y, bullet.position.z, bullet.direction.
x, bullet.direction.
y, bullet.direction.z, bullets.size()));
3762 void emitMuzzleParticles() {
3763 glm::vec3 forward(0.0f);
3764 glm::vec3 right(0.0f);
3766 buildCameraBasis(forward, right, up);
3768 const glm::vec3 muzzle = blasterMuzzleTipPosition();
3769 std::uniform_real_distribution<float> lateralJitter(-0.20f, 0.20f);
3770 std::uniform_real_distribution<float> verticalJitter(-0.12f, 0.12f);
3771 std::uniform_real_distribution<float> speedDist(8.0f, 26.0f);
3772 std::uniform_real_distribution<float> lifeDist(0.06f, 0.16f);
3773 std::uniform_real_distribution<float> warmDist(0.75f, 1.0f);
3775 constexpr int particleCount = 24;
3776 for (
int i = 0; i < particleCount; ++i) {
3777 ExplosionParticle p{};
3778 p.position = muzzle + (forward * 0.01f);
3780 glm::vec3 dir = forward + (right * lateralJitter(rng)) + (up * verticalJitter(rng));
3781 if (glm::length(dir) <= 1e-5f) {
3784 dir = glm::normalize(dir);
3787 const float speed = speedDist(rng);
3788 p.velocity = dir * speed;
3789 p.color = glm::vec3(warmDist(rng), warmDist(rng) * 0.7f, warmDist(rng) * 0.18f);
3790 p.maxLifetime = lifeDist(rng);
3791 p.size = 0.035f + (speed * 0.003f);
3792 explosionParticles.push_back(p);
3796 void updateProjectiles(
float deltaTime) {
3797 for (
size_t bulletIndex = 0; bulletIndex < bullets.size(); ++bulletIndex) {
3798 Projectile &bullet = bullets[bulletIndex];
3803 const glm::vec3 previous = bullet.position;
3804 const glm::vec3 displacement = bullet.direction * bullet.speed * deltaTime;
3805 bullet.position += displacement;
3807 bullet.distanceTraveled += glm::length(displacement);
3808 bullet.trailTimer += deltaTime;
3809 if (bullet.trailTimer >= 0.02f) {
3810 Projectile::TrailPoint point{};
3811 point.position = bullet.position;
3812 bullet.trail.push_back(point);
3813 bullet.trailTimer = 0.0f;
3815 for (Projectile::TrailPoint &point : bullet.trail) {
3816 point.lifetime += deltaTime;
3818 bullet.trail.erase(std::remove_if(bullet.trail.begin(), bullet.trail.end(), [](
const Projectile::TrailPoint &point) { return point.lifetime >= point.maxLifetime; }), bullet.trail.end());
3820 size_t collectibleIndex = 0;
3821 glm::vec3 collectibleImpact{0.0f};
3822 if (lineHitCollectible(previous, bullet.position, collectibleIndex, collectibleImpact)) {
3823 createExplosion(collectibleImpact, 5000,
false);
3824 const Collectible::Type hitType = world.collectibles()[collectibleIndex].type;
3825 const bool removed = deactivateCollectibleAt(collectibleIndex);
3826 resolveCollectibleClusters(2.0f, 3);
3831 logEnv(std::format(
"bullet {} hit {} collectible {} at ({:.2f}, {:.2f}, {:.2f}); destroyed={}", bulletIndex, collectibleTypeName(hitType), collectibleIndex, collectibleImpact.x, collectibleImpact.y, collectibleImpact.z, destroyedCount));
3835 const ProjectileTraceHit segmentHit = traceProjectileSegment(previous, bullet.position);
3836 if (segmentHit.type != ProjectileHitType::None) {
3838 if (segmentHit.type == ProjectileHitType::Floor) {
3839 createExplosion(glm::vec3(segmentHit.impact.x, 0.0f, segmentHit.impact.z), 1500,
true);
3841 logEnv(std::format(
"bullet {} hit floor at ({:.2f}, {:.2f}, {:.2f})", bulletIndex, segmentHit.impact.x, 0.0f, segmentHit.impact.z));
3845 createExplosion(segmentHit.impact, 1500,
true);
3847 logEnv(std::format(
"bullet {} hit {} at ({:.2f}, {:.2f}, {:.2f})", bulletIndex, (segmentHit.type == ProjectileHitType::Pillar) ?
"pillar" :
"wall", segmentHit.impact.x, segmentHit.impact.y, segmentHit.impact.z));
3851 if (bullet.
lifetime >= bullet.maxLifetime) {
3853 logEnv(std::format(
"bullet {} expired after {:.2f}s", bulletIndex, bullet.
lifetime));
3857 if (bullet.distanceTraveled >= bullet.maxDistance) {
3859 logEnv(std::format(
"bullet {} faded after traveling {:.2f} units", bulletIndex, bullet.distanceTraveled));
3863 bullets.erase(std::remove_if(bullets.begin(), bullets.end(), [](
const Projectile &b) { return !b.active; }), bullets.end());
3866 void updateCollectibles(
float deltaTime) {
3867 for (Collectible &obj : world.collectibles()) {
3871 obj.rotation.y += obj.rotationSpeed * deltaTime;
3872 if (obj.rotation.y > 360.0f) {
3873 obj.rotation.y -= 360.0f;
3877 collectibleClusterResolveTimer += deltaTime;
3878 if (collectibleClusterResolveTimer >= 0.75f) {
3879 collectibleClusterResolveTimer = 0.0f;
3880 resolveCollectibleClusters(2.0f, 2);
3884 void createExplosion(
const glm::vec3 &position,
int requestedCount,
bool isRed) {
3885 if (requestedCount <= 0) {
3889 constexpr float pi = 3.14159265358979323846f;
3890 std::uniform_real_distribution<float> speedDist(3.0f, 15.0f);
3891 std::uniform_real_distribution<float> angleDist(0.0f, 2.0f * pi);
3892 std::uniform_real_distribution<float> elevationDist(-(pi / 6.0f), pi / 3.0f);
3893 std::uniform_real_distribution<float> colorDist(0.7f, 1.0f);
3895 const int count = std::min(requestedCount * 2, 800);
3896 logEnv(std::format(
"explosion at ({:.2f}, {:.2f}, {:.2f}) particles={} style={}", position.x, position.y, position.z, count, isRed ?
"impact" :
"collectible"));
3897 for (
int i = 0; i < count; ++i) {
3898 ExplosionParticle p{};
3899 p.position = position;
3900 const float theta = angleDist(rng);
3901 const float phi = elevationDist(rng);
3902 const float v = speedDist(rng);
3903 p.velocity = glm::vec3(v * std::cos(phi) * std::cos(theta), v * std::sin(phi), v * std::cos(phi) * std::sin(theta));
3905 p.color = glm::vec3(colorDist(rng), colorDist(rng) * 0.3f, colorDist(rng) * 0.1f);
3907 p.color = glm::vec3(colorDist(rng), colorDist(rng) * 0.7f, colorDist(rng) * 0.2f);
3909 p.maxLifetime = 0.55f;
3910 p.size = 0.08f + (v * 0.010f);
3911 explosionParticles.push_back(p);
3915 void updateExplosions(
float deltaTime) {
3916 for (ExplosionParticle &particle : explosionParticles) {
3917 if (!particle.active) {
3920 particle.position += particle.velocity * deltaTime;
3921 particle.velocity.y -= 9.8f * deltaTime;
3923 for (
const PillarInstance &pillar : world.pillars()) {
3924 const glm::vec2 particle2d(particle.position.x, particle.position.z);
3925 const glm::vec2 pillar2d(pillar.position.x, pillar.position.z);
3926 const float distance = glm::length(particle2d - pillar2d);
3927 if (distance < pillar.radius && particle.position.y > 0.0f && particle.position.y < pillar.height) {
3928 glm::vec2 normal(1.0f, 0.0f);
3929 if (distance > 0.00001f) {
3930 normal = glm::normalize(particle2d - pillar2d);
3932 const glm::vec2 vel2d(particle.velocity.x, particle.velocity.z);
3933 const glm::vec2 reflected = vel2d - 2.0f * glm::dot(vel2d, normal) * normal;
3934 particle.velocity.x = reflected.x * 0.5f;
3935 particle.velocity.z = reflected.y * 0.5f;
3936 const glm::vec2 correction = normal * (pillar.radius - distance + 0.1f);
3937 particle.position.x += correction.x;
3938 particle.position.z += correction.y;
3942 for (
const WallSegment &wall : world.walls()) {
3943 glm::vec3 wallDir = wall.end - wall.start;
3944 const float wallLength = glm::length(wallDir);
3945 if (wallLength < 0.0001f) {
3948 wallDir = glm::normalize(wallDir);
3949 const glm::vec3 toStart = particle.position - wall.start;
3950 float projection = glm::dot(toStart, wallDir);
3951 projection = glm::clamp(projection, 0.0f, wallLength);
3952 glm::vec3 closest = wall.start + wallDir * projection;
3953 closest.y = particle.position.y;
3954 const float distance = glm::length(particle.position - closest);
3955 if (distance < 0.5f && particle.position.y >= 0.0f && particle.position.y <= wall.height) {
3956 glm::vec3 normal(1.0f, 0.0f, 0.0f);
3957 if (distance > 0.0001f) {
3958 normal = glm::normalize(particle.position - closest);
3960 particle.velocity = glm::reflect(particle.velocity, normal) * 0.5f;
3961 particle.position += normal * 0.2f;
3965 if (particle.position.y < 0.0f) {
3966 particle.position.y = 0.0f;
3967 particle.velocity.y = -particle.velocity.y * 0.3f;
3968 particle.velocity.x *= 0.8f;
3969 particle.velocity.z *= 0.8f;
3972 particle.lifetime += deltaTime;
3973 particle.size *= 0.98f;
3974 if (particle.lifetime >= particle.maxLifetime) {
3975 particle.active =
false;
3979 explosionParticles.erase(std::remove_if(explosionParticles.begin(), explosionParticles.end(), [](
const ExplosionParticle &p) { return !p.active; }), explosionParticles.end());
3982 [[nodiscard]]
bool lineHitWall(
const glm::vec3 &from,
const glm::vec3 &to, glm::vec3 &impactOut)
const {
3983 const glm::vec3 dir = to - from;
3984 constexpr float bulletRadius = 0.015f;
3985 const float travel = glm::length(dir);
3986 if (travel <= 1e-8f) {
3990 constexpr float sampleStride = 0.05f;
3991 const int steps = std::max(1,
static_cast<int>(std::ceil(travel / sampleStride)));
3992 float previousT = 0.0f;
3993 for (
int i = 0; i <= steps; ++i) {
3994 const float t =
static_cast<float>(i) /
static_cast<float>(steps);
3995 const glm::vec3 point = from + (dir * t);
3996 if (pointHitsWall3D(point, bulletRadius)) {
3997 float lo = previousT;
3999 for (
int iter = 0; iter < 10; ++iter) {
4000 const float mid = 0.5f * (lo + hi);
4001 const glm::vec3 midPoint = from + (dir * mid);
4002 if (pointHitsWall3D(midPoint, bulletRadius)) {
4008 impactOut = from + (dir * hi);
4016 [[nodiscard]]
bool lineHitPillar(
const glm::vec3 &from,
const glm::vec3 &to, glm::vec3 &impactOut)
const {
4017 const glm::vec3 dir = to - from;
4018 constexpr float bulletRadius = 0.015f;
4019 const float travel = glm::length(dir);
4020 if (travel <= 1e-8f) {
4024 constexpr float sampleStride = 0.05f;
4025 const int steps = std::max(1,
static_cast<int>(std::ceil(travel / sampleStride)));
4026 float previousT = 0.0f;
4027 for (
int i = 0; i <= steps; ++i) {
4028 const float t =
static_cast<float>(i) /
static_cast<float>(steps);
4029 const glm::vec3 point = from + (dir * t);
4030 if (pointHitsPillar3D(point, bulletRadius)) {
4031 float lo = previousT;
4033 for (
int iter = 0; iter < 10; ++iter) {
4034 const float mid = 0.5f * (lo + hi);
4035 const glm::vec3 midPoint = from + (dir * mid);
4036 if (pointHitsPillar3D(midPoint, bulletRadius)) {
4042 impactOut = from + (dir * hi);
4050 [[nodiscard]]
bool lineHitCollectible(
const glm::vec3 &from,
const glm::vec3 &to,
size_t &indexOut, glm::vec3 &impactOut)
const {
4051 const glm::vec3 dir = to - from;
4052 const float dirLen2 = glm::dot(dir, dir);
4053 constexpr float bulletRadius = 0.015f;
4054 if (dirLen2 <= 1e-8f) {
4060 size_t bestIndex = 0;
4062 const std::vector<Collectible> &collectibles = world.collectibles();
4063 for (
size_t i = 0; i < collectibles.size(); ++i) {
4064 const Collectible &obj = collectibles[i];
4072 if (obj.type == Collectible::Type::Bird) {
4073 const glm::vec3 halfExtents(obj.radius + bulletRadius);
4074 const glm::vec3 center = obj.position + obj.hitCenterOffset;
4075 const glm::vec3 boxMin = center - halfExtents;
4076 const glm::vec3 boxMax = center + halfExtents;
4080 bool slabMiss =
false;
4082 for (
int axis = 0; axis < 3; ++axis) {
4083 const float origin = from[axis];
4084 const float delta = dir[axis];
4085 const float minB = boxMin[axis];
4086 const float maxB = boxMax[axis];
4088 if (std::abs(delta) <= 1e-8f) {
4089 if (origin < minB || origin > maxB) {
4096 float t0 = (minB - origin) / delta;
4097 float t1 = (maxB - origin) / delta;
4102 tMin = std::max(tMin, t0);
4103 tMax = std::min(tMax, t1);
4115 const glm::vec3 center = obj.position + obj.hitCenterOffset;
4116 const glm::vec3 m = from - center;
4117 const float a = dirLen2;
4118 const float b = 2.0f * glm::dot(m, dir);
4119 const float hitRadius = obj.radius + bulletRadius;
4120 const float c = glm::dot(m, m) - (hitRadius * hitRadius);
4121 const float discriminant = (b * b) - (4.0f * a * c);
4122 if (discriminant >= 0.0f) {
4123 const float sqrtD = std::sqrt(discriminant);
4124 const float invDen = 1.0f / (2.0f * a);
4125 const float t0 = (-b - sqrtD) * invDen;
4126 const float t1 = (-b + sqrtD) * invDen;
4127 if (t0 >= 0.0f && t0 <= 1.0f) {
4130 }
else if (t1 >= 0.0f && t1 <= 1.0f) {
4137 if (hit && tHit >= 0.0f && tHit <= 1.0f && tHit < bestT) {
4148 indexOut = bestIndex;
4149 impactOut = from + (dir * bestT);
4153 [[nodiscard]]
bool pointHitsWall3D(
const glm::vec3 &point,
float radius)
const {
4154 const float halfThickness = (world.wallThickness() * 0.5f) + radius;
4155 const float halfThicknessSq = halfThickness * halfThickness;
4156 for (
const WallSegment &wall : world.walls()) {
4157 if (point.y < 0.0f || point.y > wall.height) {
4161 const glm::vec2 start(wall.start.x, wall.start.z);
4162 const glm::vec2 end(wall.end.x, wall.end.z);
4163 const glm::vec2 seg = end - start;
4164 const float segLen2 = glm::dot(seg, seg);
4165 if (segLen2 <= 1e-8f) {
4169 const glm::vec2 p(point.x, point.z);
4170 const glm::vec2 toPoint = p - start;
4171 const float t = glm::clamp(glm::dot(toPoint, seg) / segLen2, 0.0f, 1.0f);
4172 const glm::vec2 closest = start + (seg * t);
4173 const glm::vec2 d = p - closest;
4174 if (glm::dot(d, d) <= halfThicknessSq) {
4181 [[nodiscard]]
bool pointHitsPillar3D(
const glm::vec3 &point,
float radius)
const {
4182 for (
const PillarInstance &pillar : world.pillars()) {
4183 if (point.y < 0.0f || point.y > pillar.height) {
4187 const glm::vec2 p(point.x, point.z);
4188 const glm::vec2 c(pillar.position.x, pillar.position.z);
4189 const float hitRadius = pillar.radius + radius;
4190 const glm::vec2 d = p - c;
4191 if (glm::dot(d, d) <= (hitRadius * hitRadius)) {
4198 [[nodiscard]]
float birdGroundYForScale(
float scale)
const {
4199 const glm::vec3 extent = birdModel.modelAxisExtent();
4200 const glm::vec3 centerOffset = birdModel.modelCenterOffset();
4201 const float modelMinY = -centerOffset.y - (extent.y * 0.5f);
4202 const float clampedScale = std::max(scale, 0.0001f);
4203 return std::max(0.0f, -modelMinY * clampedScale);
4206 [[nodiscard]]
float birdHitHalfSideForScale(
float scale)
const {
4207 const glm::vec3 extent = birdModel.modelAxisExtent();
4208 const float modelSide = std::max({extent.x, extent.y, extent.z, 0.0001f});
4209 const float clampedScale = std::max(scale, 0.0001f);
4210 return 0.5f * modelSide * clampedScale;
4213 [[nodiscard]]
float saturnHitRadiusForScale(
float scale)
const {
4214 const glm::vec3 extent = saturnModel.modelAxisExtent();
4215 const float modelDiameter = std::max({extent.x, extent.y, extent.z, 0.0001f});
4216 const float clampedScale = std::max(scale, 0.0001f);
4217 return 0.5f * modelDiameter * clampedScale;
4220 [[nodiscard]] glm::vec3 saturnHitCenterOffsetForScale(
float scale)
const {
4221 const glm::vec3 centerOffset = saturnModel.modelCenterOffset();
4222 const float clampedScale = std::max(scale, 0.0001f);
4223 return glm::vec3(-centerOffset.x * clampedScale, -centerOffset.y * clampedScale, -centerOffset.z * clampedScale);
4226 [[nodiscard]] glm::vec3 birdHitCenterOffsetForScale(
float scale)
const {
4227 const glm::vec3 centerOffset = birdModel.modelCenterOffset();
4228 const float clampedScale = std::max(scale, 0.0001f);
4229 return glm::vec3(0.0f, -centerOffset.y * clampedScale, 0.0f);
4232 [[nodiscard]]
float birdSpawnClearanceRadiusForScale(
float scale)
const {
4233 const glm::vec3 extent = birdModel.modelAxisExtent();
4234 const glm::vec3 centerOffset = birdModel.modelCenterOffset();
4235 const float clampedScale = std::max(scale, 0.0001f);
4237 const float halfXFromOrigin = (extent.x * 0.5f) + std::abs(centerOffset.x);
4238 const float halfZFromOrigin = (extent.z * 0.5f) + std::abs(centerOffset.z);
4239 const float horizontalRadius = std::max(halfXFromOrigin, halfZFromOrigin) * clampedScale;
4240 return horizontalRadius + 0.05f;
4243 [[nodiscard]]
float placementRadiusForCollectible(
const Collectible &obj)
const {
4244 if (obj.type == Collectible::Type::Bird) {
4245 return std::max(obj.radius, birdSpawnClearanceRadiusForScale(obj.scale.x));
4250 void normalizeCollectiblesToModel() {
4251 for (Collectible &obj : world.collectibles()) {
4252 if (obj.type == Collectible::Type::Bird) {
4253 obj.radius = birdHitHalfSideForScale(obj.scale.x);
4254 obj.hitCenterOffset = birdHitCenterOffsetForScale(obj.scale.x);
4255 obj.position.y = birdGroundYForScale(obj.scale.x);
4257 obj.radius = saturnHitRadiusForScale(obj.scale.x);
4258 obj.hitCenterOffset = saturnHitCenterOffsetForScale(obj.scale.x);
4262 resolveCollectibleEnvironmentCollisions();
4263 resolveCollectibleOverlaps();
4264 resolveCollectibleClusters(2.0f, 5);
4267 [[nodiscard]]
bool overlapsCollectibleAt(
const glm::vec3 &candidate,
float radius,
size_t ignoreIndex,
bool includeInactive)
const {
4268 const std::vector<Collectible> &collectibles = world.collectibles();
4269 for (
size_t i = 0; i < collectibles.size(); ++i) {
4270 if (i == ignoreIndex) {
4273 const Collectible &other = collectibles[i];
4274 if (!includeInactive && !other.active) {
4278 const float separation = std::max(5.0f, other.radius + radius + 0.2f);
4279 if (glm::length(other.position - candidate) < separation) {
4286 bool relocateCollectible(
size_t index,
float minMoveDistance,
int maxAttempts) {
4287 std::vector<Collectible> &collectibles = world.collectibles();
4288 if (index >= collectibles.size()) {
4292 Collectible &obj = collectibles[index];
4293 const glm::vec3 oldPosition = obj.position;
4294 const float y = (obj.type == Collectible::Type::Bird) ? birdGroundYForScale(obj.scale.x) : 2.5f;
4295 const float placementRadius = placementRadiusForCollectible(obj);
4297 for (
int attempt = 0; attempt < maxAttempts; ++attempt) {
4298 glm::vec3 candidate{};
4299 if (!sampleNavigablePoint(y, placementRadius, candidate, 4)) {
4302 if (glm::length(candidate - oldPosition) < minMoveDistance) {
4305 if (overlapsCollectibleAt(candidate, obj.radius, index,
false)) {
4309 obj.position = candidate;
4316 void resolveCollectibleEnvironmentCollisions() {
4317 std::vector<Collectible> &collectibles = world.collectibles();
4318 for (
size_t i = 0; i < collectibles.size(); ++i) {
4319 if (!collectibles[i].
active) {
4323 const float placementRadius = placementRadiusForCollectible(collectibles[i]);
4324 if (!world.checkWallCollision(collectibles[i].position, placementRadius) && !world.checkPillarCollision(collectibles[i].position, placementRadius)) {
4328 relocateCollectible(i, 2.0f, 512);
4332 void resolveCollectibleOverlaps() {
4333 std::vector<Collectible> &collectibles = world.collectibles();
4334 for (
size_t i = 0; i < collectibles.size(); ++i) {
4335 if (!collectibles[i].
active) {
4338 if (!overlapsCollectibleAt(collectibles[i].position, collectibles[i].radius, i,
false)) {
4341 relocateCollectible(i, 1.5f, 320);
4345 void resolveCollectibleClusters(
float minVisualSeparation,
int passes) {
4346 if (minVisualSeparation <= 0.0f || passes <= 0) {
4350 std::vector<Collectible> &collectibles = world.collectibles();
4351 const float minVisualSeparationSq = minVisualSeparation * minVisualSeparation;
4352 for (
int pass = 0; pass < passes; ++pass) {
4353 bool movedAny =
false;
4354 for (
size_t i = 0; i < collectibles.size(); ++i) {
4355 if (!collectibles[i].
active) {
4359 for (
size_t j = i + 1; j < collectibles.size(); ++j) {
4360 if (!collectibles[j].
active) {
4364 const glm::vec3 delta = collectibles[j].position - collectibles[i].position;
4365 if (glm::dot(delta, delta) >= minVisualSeparationSq) {
4369 if (relocateCollectible(j, minVisualSeparation, 512)) {
4381 void disperseNearbyCollectibles(
const glm::vec3 ¢er,
float radius,
size_t ignoreIndex) {
4382 std::vector<Collectible> &collectibles = world.collectibles();
4383 for (
size_t i = 0; i < collectibles.size(); ++i) {
4384 if (i == ignoreIndex) {
4387 if (!collectibles[i].
active) {
4390 if (glm::length(collectibles[i].position - center) > radius) {
4394 relocateCollectible(i, std::max(3.0f, radius), 256);
4398 [[nodiscard]]
bool deactivateCollectibleAt(
size_t index) {
4399 std::vector<Collectible> &collectibles = world.collectibles();
4400 if (index >= collectibles.size()) {
4404 Collectible &obj = collectibles[index];
4413 std::string assetRoot;
4414 std::string shaderRoot;
4415 std::string modelRoot;
4418 mxvk::VKAbstractModel floorModel{};
4419 RawWallRenderer rawWallRenderer{};
4420 RawPillarRenderer rawPillarRenderer{};
4421 mxvk::VKAbstractModel saturnModel{};
4422 mxvk::VKAbstractModel birdModel{};
4423 mxvk::VKAbstractModel blasterModel{};
4424 mxvk::VKAbstractModel bulletModel{};
4426 VkPipelineLayout pointPipelineLayout = VK_NULL_HANDLE;
4427 VkPipeline pointPipeline = VK_NULL_HANDLE;
4428 VkBuffer pointVertexBuffer = VK_NULL_HANDLE;
4429 VkDeviceMemory pointVertexMemory = VK_NULL_HANDLE;
4430 void *pointVertexMapped =
nullptr;
4431 size_t maxPointVertices = 200000;
4432 std::string pointParticleVertSpv{};
4433 std::string pointParticleFragSpv{};
4434 std::string modelVertSpv{};
4435 std::string pillarVertSpv{};
4436 std::string wallFragSpv{};
4437 std::string floorFragSpv{};
4438 std::string pillarFragSpv{};
4439 std::string objectFragSpv{};
4440 std::string bulletFragSpv{};
4441 std::string postProcessingShaderPath{};
4442 std::vector<std::string> postProcessingShaders{};
4443 mxvk::VK_Sprite *postProcessingSprite =
nullptr;
4444 int postProcessingShaderIndex = 0;
4445 uint32_t postProcessingFrameCount = 0;
4446 std::chrono::steady_clock::time_point postProcessingStartTime{std::chrono::steady_clock::now()};
4447 std::chrono::steady_clock::time_point previousPostProcessingTime{postProcessingStartTime};
4449 std::vector<Projectile> bullets{};
4450 std::vector<ExplosionParticle> explosionParticles{};
4451 std::mt19937
rng{std::random_device{}()};
4453 glm::vec3 cameraPos{0.0f, 1.7f, 0.0f};
4454 glm::vec3 cameraFront{0.0f, 0.0f, -1.0f};
4457 bool mouseCapture =
true;
4458 bool firstMouse =
true;
4459 bool suppressProjectileOnNextLeftDown =
false;
4460 bool showFps =
true;
4461 float mouseSensitivity = 0.15f;
4463 float jumpVelocity = 0.0f;
4464 float gravity = 0.015f;
4465 float collectibleClusterResolveTimer = 0.0f;
4466 uint32_t destroyedCount = 0;
4468 SDL_Gamepad *gamepad =
nullptr;
4469 SDL_JoystickID gamepadId = 0;
4470 int stickDeadZone = 8000;
4471 float controllerLookSensitivity = 2.0f;
4473 std::chrono::steady_clock::time_point lastTick{std::chrono::steady_clock::now()};
4484 std::cerr << std::format(
"mxvk: Exception: {}\n", e.
text());
4485 return EXIT_FAILURE;
4487 std::cerr << std::format(
"mxvk: Argument Exception: {}\n", e.
text());
4488 return EXIT_FAILURE;
4491 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.
void event(SDL_Event &e) override
Handle one SDL event.
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.
void setFont(const std::string &fontPath, int fontSize=24)
Set the active text-render font.
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.
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