render map objects: decode compiled W3DMesh art, skin and draw over terrain

Read the buildings and props a map places from its compiled art (the
uncompressed worldbuilder stream + embedded DDS textures):

- ra3.models: BAB asset-stream parser (lazy slices), W3DMesh + D3DHierarchy
  decode, DDS (DXT1/3/5 + uncompressed) decode, per-mesh bone remap and
  bind-pose single-joint skinning, flattened to one world-space triangle soup.
- ra3.map: parse the ObjectsList chunk into (type, x, y, z, angle).
- ra3.terrain: render3d rasterises the scene over the raymarched terrain with
  a z-buffer; gpu_terrain carries the scene for the GPU backends.
- ra3.vulkan: second pipeline + depth attachment, terrain.frag writes
  gl_FragDepth, and a small depth bias keeps ground decals from z-fighting.
- objects.fx shaders (compiled to SPIR-V), embedded like scene/terrain.

Only opaque parts are drawn: FX-light billboards (DefaultW3D.fx / BasicW3D.fx)
and damage-fill shells (BuildingsGenericDamageFill.fx) are skipped, since the
latter paint the wrecked interior (e.g. orange CBBuilding_Wood) over the shell.

Ground-decal meshes with no diffuse role and the Road templates themselves are
still not drawn.
This commit is contained in:
EnderTheCoder
2026-09-29 18:08:11 +08:00
parent 6e5df350c7
commit 70f35d8382
18 changed files with 2027 additions and 45 deletions
+373 -17
View File
@@ -92,6 +92,7 @@ export namespace ra3::vulkan {
if (!this->create_device()) return false;
if (!this->create_swapchain()) return false;
if (!this->create_render_pass()) return false;
if (!this->create_framebuffers()) return false;
if (!this->create_pipeline()) return false;
if (!this->create_commands()) return false;
if (!this->create_sync()) return false;
@@ -341,6 +342,12 @@ export namespace ra3::vulkan {
if (!this->create_terrain_pipeline()) return false;
if (!this->create_terrain_images(terrain)) return false;
if (!this->create_terrain_descriptors()) return false;
if (!terrain.objects.empty()) {
if (!this->create_object_pipeline()) return false;
if (!this->create_object_buffers(terrain)) return false;
if (!this->create_object_descriptors()) return false;
objects_ready_ = true;
}
terrain_ready_ = true;
}
if (overlay.label_changed) this->update_overlay(overlay_label_, overlay_label_set_, overlay_label_w_, overlay_label_h_, overlay.label);
@@ -669,6 +676,11 @@ export namespace ra3::vulkan {
VkPipelineMultisampleStateCreateInfo multisample{};
multisample.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
multisample.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
VkPipelineDepthStencilStateCreateInfo depth_stencil{};
depth_stencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
depth_stencil.depthTestEnable = VK_TRUE;
depth_stencil.depthWriteEnable = VK_TRUE;
depth_stencil.depthCompareOp = VK_COMPARE_OP_LESS_OR_EQUAL;
VkPipelineColorBlendAttachmentState blend_attachment{};
blend_attachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
VkPipelineColorBlendStateCreateInfo blend{};
@@ -690,6 +702,7 @@ export namespace ra3::vulkan {
pipeline_info.pViewportState = &viewport;
pipeline_info.pRasterizationState = &raster;
pipeline_info.pMultisampleState = &multisample;
pipeline_info.pDepthStencilState = &depth_stencil;
pipeline_info.pColorBlendState = &blend;
pipeline_info.pDynamicState = &dynamic;
pipeline_info.layout = terrain_pipeline_layout_;
@@ -737,6 +750,197 @@ export namespace ra3::vulkan {
return true;
}
/** Host-visible vertex/index buffer, filled once (the map's geometry is static). */
[[nodiscard]] auto make_host_buffer(const void *data, VkDeviceSize bytes, VkBufferUsageFlags usage, VkBuffer &buffer,
VkDeviceMemory &memory) -> bool {
VkBufferCreateInfo info{};
info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
info.size = bytes;
info.usage = usage;
info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
if (!detail::check(vkCreateBuffer(device_, &info, nullptr, &buffer), "vkCreateBuffer(object)")) return false;
VkMemoryRequirements requirements{};
vkGetBufferMemoryRequirements(device_, buffer, &requirements);
VkMemoryAllocateInfo allocate{};
allocate.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
allocate.allocationSize = requirements.size;
allocate.memoryTypeIndex = this->memory_type(requirements.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
if (!detail::check(vkAllocateMemory(device_, &allocate, nullptr, &memory), "vkAllocateMemory(object)")) return false;
if (!detail::check(vkBindBufferMemory(device_, buffer, memory, 0), "vkBindBufferMemory(object)")) return false;
void *mapped = nullptr;
if (!detail::check(vkMapMemory(device_, memory, 0, bytes, 0, &mapped), "vkMapMemory(object)")) return false;
std::memcpy(mapped, data, static_cast<std::size_t>(bytes));
vkUnmapMemory(device_, memory);
return true;
}
/** Upload the static-map geometry and its texture array. */
[[nodiscard]] auto create_object_buffers(const ra3::terrain::gpu_terrain &terrain) -> bool {
const auto &scene = terrain.objects;
if (scene.empty()) return true;
const auto vertex_bytes = static_cast<VkDeviceSize>(scene.vertices.size()) * sizeof(ra3::models::vertex);
const auto index_bytes = static_cast<VkDeviceSize>(scene.indices.size()) * sizeof(uint32_t);
if (!this->make_host_buffer(scene.vertices.data(), vertex_bytes, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, object_vertex_buffer_, object_vertex_memory_)) return false;
if (!this->make_host_buffer(scene.indices.data(), index_bytes, VK_BUFFER_USAGE_INDEX_BUFFER_BIT, object_index_buffer_, object_index_memory_)) return false;
object_index_count_ = static_cast<uint32_t>(scene.indices.size());
const auto size = std::max(1U, scene.texture_size);
const auto layers = std::max<size_t>(1U, scene.textures.size());
std::vector<uint32_t> pixels(static_cast<size_t>(size) * size * layers, 0xFFFFFFFFU);
for (size_t layer = 0; layer < scene.textures.size(); ++layer) {
const auto &texture = scene.textures[layer];
if (texture.empty()) continue;
// The scene already resized every texture to `texture_size`.
for (uint32_t y = 0; y < std::min(size, texture.height()); ++y) {
for (uint32_t x = 0; x < std::min(size, texture.width()); ++x) {
pixels[(static_cast<size_t>(layer) * size + y) * size + x] = texture.data()[static_cast<size_t>(y) * texture.width() + x];
}
}
}
return this->make_gpu_image(object_texture_, size, size, VK_FORMAT_B8G8R8A8_UNORM, pixels.data(),
static_cast<VkDeviceSize>(pixels.size()) * 4U, this->mip_count(size), VK_FILTER_LINEAR, VK_SAMPLER_MIPMAP_MODE_LINEAR,
static_cast<uint32_t>(layers), VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE);
}
/** Full mip chain length for a square texture. */
[[nodiscard]] static auto mip_count(uint32_t size) -> uint32_t {
uint32_t mips = 1U;
while (size > 1U) {
size >>= 1U;
++mips;
}
return mips;
}
[[nodiscard]] auto create_object_pipeline() -> bool {
VkPushConstantRange push_range{};
push_range.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
push_range.size = sizeof(float) * 20U;
VkPipelineLayoutCreateInfo layout_info{};
layout_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
layout_info.setLayoutCount = 1U;
layout_info.pSetLayouts = &descriptor_layout_; // reused: one combined image sampler
layout_info.pushConstantRangeCount = 1U;
layout_info.pPushConstantRanges = &push_range;
if (!detail::check(vkCreatePipelineLayout(device_, &layout_info, nullptr, &object_pipeline_layout_), "vkCreatePipelineLayout(object)")) return false;
VkShaderModule vertex = VK_NULL_HANDLE;
VkShaderModule fragment = VK_NULL_HANDLE;
if (!this->make_shader_module(ra3_shaders::object_vert_spv, ra3_shaders::object_vert_spv_words, vertex)) return false;
if (!this->make_shader_module(ra3_shaders::object_frag_spv, ra3_shaders::object_frag_spv_words, fragment)) return false;
VkPipelineShaderStageCreateInfo stages[2]{};
stages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
stages[0].stage = VK_SHADER_STAGE_VERTEX_BIT;
stages[0].module = vertex;
stages[0].pName = "main";
stages[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
stages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT;
stages[1].module = fragment;
stages[1].pName = "main";
VkVertexInputBindingDescription binding{};
binding.binding = 0U;
binding.stride = sizeof(ra3::models::vertex);
binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
VkVertexInputAttributeDescription attributes[4]{};
attributes[0] = {0U, 0U, VK_FORMAT_R32G32B32_SFLOAT, 0U};
attributes[1] = {1U, 0U, VK_FORMAT_R32G32B32_SFLOAT, 12U};
attributes[2] = {2U, 0U, VK_FORMAT_R32G32_SFLOAT, 24U};
attributes[3] = {3U, 0U, VK_FORMAT_R32_SFLOAT, 32U};
static_assert(sizeof(ra3::models::vertex) == 36U, "object vertex layout changed");
VkPipelineVertexInputStateCreateInfo vertex_input{};
vertex_input.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
vertex_input.vertexBindingDescriptionCount = 1U;
vertex_input.pVertexBindingDescriptions = &binding;
vertex_input.vertexAttributeDescriptionCount = 4U;
vertex_input.pVertexAttributeDescriptions = attributes;
VkPipelineInputAssemblyStateCreateInfo assembly{};
assembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
assembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
VkPipelineViewportStateCreateInfo viewport{};
viewport.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
viewport.viewportCount = 1U;
viewport.scissorCount = 1U;
VkPipelineRasterizationStateCreateInfo raster{};
raster.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
raster.polygonMode = VK_POLYGON_MODE_FILL;
raster.cullMode = VK_CULL_MODE_NONE;
raster.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
raster.lineWidth = 1.0F;
// Ground decals (sidewalks/roads/deck pieces) sit exactly on the
// terrain; a small negative depth bias keeps them from z-fighting.
raster.depthBiasEnable = VK_TRUE;
raster.depthBiasConstantFactor = -1.0F;
raster.depthBiasSlopeFactor = -1.0F;
VkPipelineMultisampleStateCreateInfo multisample{};
multisample.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
multisample.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
VkPipelineDepthStencilStateCreateInfo depth_stencil{};
depth_stencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
depth_stencil.depthTestEnable = VK_TRUE;
depth_stencil.depthWriteEnable = VK_TRUE;
depth_stencil.depthCompareOp = VK_COMPARE_OP_LESS_OR_EQUAL;
VkPipelineColorBlendAttachmentState blend_attachment{};
blend_attachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
blend_attachment.blendEnable = VK_FALSE;
VkPipelineColorBlendStateCreateInfo blend{};
blend.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
blend.attachmentCount = 1U;
blend.pAttachments = &blend_attachment;
const VkDynamicState dynamic_states[] = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
VkPipelineDynamicStateCreateInfo dynamic{};
dynamic.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
dynamic.dynamicStateCount = 2U;
dynamic.pDynamicStates = dynamic_states;
VkGraphicsPipelineCreateInfo pipeline_info{};
pipeline_info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
pipeline_info.stageCount = 2U;
pipeline_info.pStages = stages;
pipeline_info.pVertexInputState = &vertex_input;
pipeline_info.pInputAssemblyState = &assembly;
pipeline_info.pViewportState = &viewport;
pipeline_info.pRasterizationState = &raster;
pipeline_info.pMultisampleState = &multisample;
pipeline_info.pDepthStencilState = &depth_stencil;
pipeline_info.pColorBlendState = &blend;
pipeline_info.pDynamicState = &dynamic;
pipeline_info.layout = object_pipeline_layout_;
pipeline_info.renderPass = render_pass_;
pipeline_info.subpass = 0U;
const auto created = detail::check(vkCreateGraphicsPipelines(device_, VK_NULL_HANDLE, 1U, &pipeline_info, nullptr, &object_pipeline_),
"vkCreateGraphicsPipelines(object)");
vkDestroyShaderModule(device_, vertex, nullptr);
vkDestroyShaderModule(device_, fragment, nullptr);
return created;
}
[[nodiscard]] auto create_object_descriptors() -> bool {
if (object_texture_.view == VK_NULL_HANDLE) return false;
VkDescriptorSetAllocateInfo set_info{};
set_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
set_info.descriptorPool = descriptor_pool_;
set_info.descriptorSetCount = 1U;
set_info.pSetLayouts = &descriptor_layout_;
if (!detail::check(vkAllocateDescriptorSets(device_, &set_info, &object_set_), "vkAllocateDescriptorSets(object)")) return false;
VkDescriptorImageInfo info{};
info.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
info.imageView = object_texture_.view;
info.sampler = object_texture_.sampler;
VkWriteDescriptorSet write{};
write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
write.dstSet = object_set_;
write.dstBinding = 0U;
write.descriptorCount = 1U;
write.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
write.pImageInfo = &info;
vkUpdateDescriptorSets(device_, 1U, &write, 0U, nullptr);
return true;
}
[[nodiscard]] auto draw_terrain(const ra3::terrain::gpu_terrain &terrain, const ra3::render::camera3d &camera, float time_s, float aspect) -> bool {
vkWaitForFences(device_, 1U, &in_flight_[current_frame_], VK_TRUE, UINT64_MAX);
@@ -754,13 +958,16 @@ export namespace ra3::vulkan {
VkClearValue clear{};
clear.color = {{0.45F, 0.55F, 0.70F, 1.0F}};
VkClearValue depth_clear{};
depth_clear.depthStencil = {1.0F, 0U};
const VkClearValue clears[2] = {clear, depth_clear};
VkRenderPassBeginInfo render_pass_begin{};
render_pass_begin.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
render_pass_begin.renderPass = render_pass_;
render_pass_begin.framebuffer = framebuffers_[image_index];
render_pass_begin.renderArea = {{0U, 0U}, swapchain_extent_};
render_pass_begin.clearValueCount = 1U;
render_pass_begin.pClearValues = &clear;
render_pass_begin.clearValueCount = 2U;
render_pass_begin.pClearValues = clears;
vkCmdBeginRenderPass(cmd, &render_pass_begin, VK_SUBPASS_CONTENTS_INLINE);
VkViewport viewport{0.0F, 0.0F, static_cast<float>(swapchain_extent_.width), static_cast<float>(swapchain_extent_.height), 0.0F, 1.0F};
@@ -793,6 +1000,49 @@ export namespace ra3::vulkan {
vkCmdPushConstants(cmd, terrain_pipeline_layout_, VK_SHADER_STAGE_FRAGMENT_BIT, 0U, sizeof(push), push);
vkCmdDraw(cmd, 3U, 1U, 0U, 0U);
// Static-map models (buildings/props) depth-test against the terrain.
if (objects_ready_ && object_index_count_ > 0U) {
const auto z_scale = terrain.z_scale;
const auto world_w = static_cast<float>(terrain.width) * 10.0F;
const auto world_h = static_cast<float>(terrain.height) * 10.0F;
const auto sample = [&](float wx, float wy) -> float {
if (wx < 0.0F || wy < 0.0F || wx >= world_w || wy >= world_h) return -1.0e9F;
const auto cx = std::min(terrain.width - 1U, static_cast<uint32_t>(wx / 10.0F));
const auto cy = std::min(terrain.height - 1U, static_cast<uint32_t>((world_h - wy) / 10.0F));
return static_cast<float>(terrain.heights[static_cast<size_t>(cy) * terrain.width + cx]) * z_scale;
};
float target_z = sample(camera.target_x, camera.target_y);
if (target_z < -1.0e8F) target_z = 0.0F;
const auto pitch = std::clamp(camera.pitch, 0.15F, 1.45F);
const auto fov = std::clamp(camera.fov, 0.3F, 1.4F);
const auto cp = std::cos(pitch);
const float fwd[3] = {cp * std::sin(camera.yaw), cp * std::cos(camera.yaw), -std::sin(pitch)};
float right[3] = {fwd[1], -fwd[0], 0.0F};
const auto rl = std::sqrt(right[0] * right[0] + right[1] * right[1]);
right[0] /= rl;
right[1] /= rl;
float up[3] = {right[1] * fwd[2], -right[0] * fwd[2], right[0] * fwd[1] - right[1] * fwd[0]};
const auto ul = std::sqrt(up[0] * up[0] + up[1] * up[1] + up[2] * up[2]);
up[0] /= ul;
up[1] /= ul;
up[2] /= ul;
const auto dist = camera.height / std::sin(pitch);
const float cam[3] = {camera.target_x - fwd[0] * dist, camera.target_y - fwd[1] * dist, target_z + camera.height - fwd[2] * dist};
const auto th = std::tan(fov * 0.5F);
const float obj_push[20] = {cam[0], cam[1], cam[2], 0.0F,
fwd[0], fwd[1], fwd[2], 0.0F,
right[0], right[1], right[2], th,
up[0], up[1], up[2], th * aspect,
0.45F, 0.35F, 0.82F, 0.38F};
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, object_pipeline_);
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, object_pipeline_layout_, 0U, 1U, &object_set_, 0U, nullptr);
vkCmdPushConstants(cmd, object_pipeline_layout_, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0U, sizeof(obj_push), obj_push);
const VkDeviceSize offset = 0U;
vkCmdBindVertexBuffers(cmd, 0U, 1U, &object_vertex_buffer_, &offset);
vkCmdBindIndexBuffer(cmd, object_index_buffer_, 0U, VK_INDEX_TYPE_UINT32);
vkCmdDrawIndexed(cmd, object_index_count_, 1U, 0U, 0, 0U);
}
// Overlays (top-left FPS label, bottom-right minimap) use the image
// pipeline with alpha blending, drawn into the same render pass.
const auto draw_overlay = [&](const gpu_image &overlay, VkDescriptorSet set, uint32_t w, uint32_t h, float x, float y) {
@@ -861,6 +1111,27 @@ export namespace ra3::vulkan {
terrain_set_ = VK_NULL_HANDLE;
overlay_label_set_ = VK_NULL_HANDLE;
overlay_minimap_set_ = VK_NULL_HANDLE;
if (object_vertex_buffer_ != VK_NULL_HANDLE) vkDestroyBuffer(device_, object_vertex_buffer_, nullptr);
if (object_index_buffer_ != VK_NULL_HANDLE) vkDestroyBuffer(device_, object_index_buffer_, nullptr);
if (object_vertex_memory_ != VK_NULL_HANDLE) vkFreeMemory(device_, object_vertex_memory_, nullptr);
if (object_index_memory_ != VK_NULL_HANDLE) vkFreeMemory(device_, object_index_memory_, nullptr);
if (object_texture_.sampler != VK_NULL_HANDLE) vkDestroySampler(device_, object_texture_.sampler, nullptr);
if (object_texture_.view != VK_NULL_HANDLE) vkDestroyImageView(device_, object_texture_.view, nullptr);
if (object_texture_.image != VK_NULL_HANDLE) vkDestroyImage(device_, object_texture_.image, nullptr);
if (object_texture_.memory != VK_NULL_HANDLE) vkFreeMemory(device_, object_texture_.memory, nullptr);
if (object_pipeline_ != VK_NULL_HANDLE) vkDestroyPipeline(device_, object_pipeline_, nullptr);
if (object_pipeline_layout_ != VK_NULL_HANDLE) vkDestroyPipelineLayout(device_, object_pipeline_layout_, nullptr);
object_vertex_buffer_ = VK_NULL_HANDLE;
object_index_buffer_ = VK_NULL_HANDLE;
object_vertex_memory_ = VK_NULL_HANDLE;
object_index_memory_ = VK_NULL_HANDLE;
object_texture_ = {};
object_pipeline_ = VK_NULL_HANDLE;
object_pipeline_layout_ = VK_NULL_HANDLE;
object_set_ = VK_NULL_HANDLE;
object_index_count_ = 0U;
objects_ready_ = false;
}
@@ -1009,23 +1280,78 @@ export namespace ra3::vulkan {
}
framebuffers_.resize(actual);
for (uint32_t i = 0; i < actual; ++i) {
VkImageView attachments[] = {swapchain_views_[i]};
return true;
}
/** Create one color+depth framebuffer per swapchain image (needs `render_pass_`). */
[[nodiscard]] auto create_framebuffers() -> bool {
this->create_depth_resources();
for (size_t i = 0; i < framebuffers_.size(); ++i) {
VkImageView attachments[] = {swapchain_views_[i], depth_views_[i]};
VkFramebufferCreateInfo framebuffer_info{};
framebuffer_info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
framebuffer_info.renderPass = render_pass_;
framebuffer_info.attachmentCount = 1U;
framebuffer_info.attachmentCount = 2U;
framebuffer_info.pAttachments = attachments;
framebuffer_info.width = extent.width;
framebuffer_info.height = extent.height;
framebuffer_info.width = swapchain_extent_.width;
framebuffer_info.height = swapchain_extent_.height;
framebuffer_info.layers = 1U;
if (!detail::check(vkCreateFramebuffer(device_, &framebuffer_info, nullptr, &framebuffers_[i]), "vkCreateFramebuffer")) return false;
}
return true;
}
/** Allocate a depth image + view per swapchain image (static-map models depth-test against the terrain). */
auto create_depth_resources() -> void {
this->destroy_depth_resources();
depth_images_.resize(swapchain_images_.size());
depth_memories_.resize(swapchain_images_.size());
depth_views_.resize(swapchain_images_.size());
for (size_t i = 0; i < swapchain_images_.size(); ++i) {
VkImageCreateInfo image_info{};
image_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
image_info.imageType = VK_IMAGE_TYPE_2D;
image_info.extent = {swapchain_extent_.width, swapchain_extent_.height, 1U};
image_info.mipLevels = 1U;
image_info.arrayLayers = 1U;
image_info.format = VK_FORMAT_D32_SFLOAT;
image_info.tiling = VK_IMAGE_TILING_OPTIMAL;
image_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
image_info.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
image_info.samples = VK_SAMPLE_COUNT_1_BIT;
image_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
if (vkCreateImage(device_, &image_info, nullptr, &depth_images_[i]) != VK_SUCCESS) return;
VkMemoryRequirements requirements{};
vkGetImageMemoryRequirements(device_, depth_images_[i], &requirements);
VkMemoryAllocateInfo allocate{};
allocate.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
allocate.allocationSize = requirements.size;
allocate.memoryTypeIndex = this->memory_type(requirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
if (vkAllocateMemory(device_, &allocate, nullptr, &depth_memories_[i]) != VK_SUCCESS) return;
vkBindImageMemory(device_, depth_images_[i], depth_memories_[i], 0);
VkImageViewCreateInfo view_info{};
view_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
view_info.image = depth_images_[i];
view_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
view_info.format = VK_FORMAT_D32_SFLOAT;
view_info.subresourceRange = {VK_IMAGE_ASPECT_DEPTH_BIT, 0U, 1U, 0U, 1U};
if (vkCreateImageView(device_, &view_info, nullptr, &depth_views_[i]) != VK_SUCCESS) return;
}
}
auto destroy_depth_resources() -> void {
if (device_ == VK_NULL_HANDLE) return;
for (const auto view: depth_views_) vkDestroyImageView(device_, view, nullptr);
for (const auto image: depth_images_) vkDestroyImage(device_, image, nullptr);
for (const auto memory: depth_memories_) vkFreeMemory(device_, memory, nullptr);
depth_views_.clear();
depth_images_.clear();
depth_memories_.clear();
}
auto cleanup_swapchain() -> void {
for (const auto framebuffer: framebuffers_) vkDestroyFramebuffer(device_, framebuffer, nullptr);
this->destroy_depth_resources();
for (const auto view: swapchain_views_) vkDestroyImageView(device_, view, nullptr);
framebuffers_.clear();
swapchain_views_.clear();
@@ -1041,7 +1367,7 @@ export namespace ra3::vulkan {
if (width == 0 || height == 0) return false;
vkDeviceWaitIdle(device_);
this->cleanup_swapchain();
return this->create_swapchain();
return this->create_swapchain() && this->create_framebuffers();
}
// ---- pipeline --------------------------------------------------------
@@ -1058,22 +1384,36 @@ export namespace ra3::vulkan {
color.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
VkAttachmentReference color_ref{0U, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL};
VkAttachmentDescription depth{};
depth.format = VK_FORMAT_D32_SFLOAT;
depth.samples = VK_SAMPLE_COUNT_1_BIT;
depth.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
depth.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
depth.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
depth.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
depth.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
depth.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
VkAttachmentReference depth_ref{1U, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL};
VkSubpassDescription subpass{};
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
subpass.colorAttachmentCount = 1U;
subpass.pColorAttachments = &color_ref;
subpass.pDepthStencilAttachment = &depth_ref;
VkSubpassDependency dependency{};
dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
dependency.dstSubpass = 0U;
dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT;
dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT;
dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
const VkAttachmentDescription attachments[] = {color, depth};
VkRenderPassCreateInfo render_pass_info{};
render_pass_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
render_pass_info.attachmentCount = 1U;
render_pass_info.pAttachments = &color;
render_pass_info.attachmentCount = 2U;
render_pass_info.pAttachments = attachments;
render_pass_info.subpassCount = 1U;
render_pass_info.pSubpasses = &subpass;
render_pass_info.dependencyCount = 1U;
@@ -1211,10 +1551,10 @@ export namespace ra3::vulkan {
command_buffers_.resize(frames_in_flight);
if (!detail::check(vkAllocateCommandBuffers(device_, &allocate, command_buffers_.data()), "vkAllocateCommandBuffers")) return false;
VkDescriptorPoolSize pool_size{VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 4U};
VkDescriptorPoolSize pool_size{VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 8U};
VkDescriptorPoolCreateInfo descriptor_pool_info{};
descriptor_pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
descriptor_pool_info.maxSets = 4U;
descriptor_pool_info.maxSets = 8U;
descriptor_pool_info.poolSizeCount = 1U;
descriptor_pool_info.pPoolSizes = &pool_size;
if (!detail::check(vkCreateDescriptorPool(device_, &descriptor_pool_info, nullptr, &descriptor_pool_), "vkCreateDescriptorPool")) return false;
@@ -1307,13 +1647,16 @@ export namespace ra3::vulkan {
VkClearValue clear{};
clear.color = {{0.05F, 0.06F, 0.08F, 1.0F}};
VkClearValue depth_clear{};
depth_clear.depthStencil = {1.0F, 0U};
const VkClearValue clears[2] = {clear, depth_clear};
VkRenderPassBeginInfo render_pass_begin{};
render_pass_begin.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
render_pass_begin.renderPass = render_pass_;
render_pass_begin.framebuffer = framebuffers_[image_index];
render_pass_begin.renderArea = {{0U, 0U}, swapchain_extent_};
render_pass_begin.clearValueCount = 1U;
render_pass_begin.pClearValues = &clear;
render_pass_begin.clearValueCount = 2U;
render_pass_begin.pClearValues = clears;
vkCmdBeginRenderPass(cmd, &render_pass_begin, VK_SUBPASS_CONTENTS_INLINE);
VkViewport viewport{0.0F, 0.0F, static_cast<float>(swapchain_extent_.width), static_cast<float>(swapchain_extent_.height), 0.0F, 1.0F};
@@ -1433,6 +1776,9 @@ export namespace ra3::vulkan {
std::vector<VkImage> swapchain_images_;
std::vector<VkImageView> swapchain_views_;
std::vector<VkFramebuffer> framebuffers_;
std::vector<VkImage> depth_images_;
std::vector<VkDeviceMemory> depth_memories_;
std::vector<VkImageView> depth_views_;
VkRenderPass render_pass_ = VK_NULL_HANDLE;
VkDescriptorSetLayout descriptor_layout_ = VK_NULL_HANDLE;
VkPipelineLayout pipeline_layout_ = VK_NULL_HANDLE;
@@ -1464,6 +1810,16 @@ export namespace ra3::vulkan {
VkPipeline terrain_pipeline_ = VK_NULL_HANDLE;
VkDescriptorPool terrain_pool_ = VK_NULL_HANDLE;
VkDescriptorSet terrain_set_ = VK_NULL_HANDLE;
VkBuffer object_vertex_buffer_ = VK_NULL_HANDLE;
VkDeviceMemory object_vertex_memory_ = VK_NULL_HANDLE;
VkBuffer object_index_buffer_ = VK_NULL_HANDLE;
VkDeviceMemory object_index_memory_ = VK_NULL_HANDLE;
uint32_t object_index_count_ = 0U;
gpu_image object_texture_;
VkPipelineLayout object_pipeline_layout_ = VK_NULL_HANDLE;
VkPipeline object_pipeline_ = VK_NULL_HANDLE;
VkDescriptorSet object_set_ = VK_NULL_HANDLE;
bool objects_ready_ = false;
std::vector<VkSemaphore> image_available_;
std::vector<VkSemaphore> render_finished_;
std::vector<VkFence> in_flight_;